RSpec cannot detect singleton error while trying to fake get and put for hash elements

I have a book model that is a ruby โ€‹โ€‹script that prices certain predefined book names mentioned in the program. Here's what the book model looks like: -

class Book attr_accessor :books def initialize books puts "Welcome to setting book price program" @books = books end def get_prices puts "Please enter appropriate price for each book item:-" count = 0 @books = @books.inject({}) { |hash, book| print "#{book.first}: " price = STDIN.gets.chomp while (price !~ /^[1-9]\d*$/ && price != "second hand") puts "Price can't be 0 or a negative integer or in decimal format or alphanumeric. \nPlease input appropriate price in integer" price = STDIN.gets.chomp #gets.chomp - throws error end price == "second hand" ? price = "100" : price #takes a default price hash[book.first] = price.to_i hash } end end books = {"The Last Samurai" => nil, "Ruby Cookbook" => nil, "Rails Recipes" => nil, "Agile Development with Rails" => nil, "Harry Potter and the Deathly Hallows" => nil} book_details = Book.new(books) book_details.get_prices puts "\n*******Books Details:#{book_details.books}******\n" 

I am trying to write a test case that verifies that the price is entered correctly for each element of the book. If the price is entered incorrectly, it should ask the user to enter the price correctly. The program does an excellent job. But I encounter difficulties when I try to mock this behavior with RSpec.

 require 'spec_helper' describe Book do before :each do books = {"The Last Samurai" => nil, "Ruby Cookbook" => nil, "Rails Recipes" => nil, "Agile Development with Rails" => nil, "Harry Potter and the Deathly Hallows" => nil} @book = Book.new(books) end describe "#new" do it "Should be an instance of the Book" do @book.should be_an_instance_of Book end end describe "#getprice" do it "Should get the price in the correct format or else return appropriate error" do puts "\n************************************************************************\n" book_obj = @book STDOUT.should_receive(:puts).and_return("Welcome to setting book price program") book_obj.get_prices.should_not be_nil book_obj.books["The Last Samurai"].stub!(:gets) {"40"} #trying to set the value for one book using Hash book_obj.books["The Last Samurai"].should == 40 #verifying the value set for a particular key is accurate end end end 

You can even clone this code from Github to try it on your part. I am using Ruby 1.9.3 and rspec 2.11.0

 The error that I'm getting currently is:- Failures: 1) Book#getprice Should get the price in the correct format or else return appropriate error Failure/Error: book_obj.books["The Last Samurai"].stub!(:gets) {"40"} #trying to set the value for one book using Hash TypeError: can't define singleton # ./spec/book_spec.rb:31:in `block (3 levels) in <top (required)>' Finished in 7.61 seconds 2 examples, 1 failure Failed examples: rspec ./spec/book_spec.rb:21 # Book#getprice Should get the price in the correct format or else return appropriate error 

UPDATE QUESTION

If the user is entered incorrectly, with the test case below, I get the following error. How can I handle this correctly? I tried several options, but all of them seem to fail. Please see the Comment for each parameter as part of a specification fragment.

  it "Incorrect input format should return error message asking user to re input" do puts "\n************************************************************************\n" book_obj = @book STDIN.stub(:gets) { "40abc" } #book_obj.get_prices.should be_nil --> adding this line of code goes into an infinite loop with the error message below #Price cannot be 0 or a negative integer or in decimal format or alphanumeric. \nPlease input appropriate duration in integer\n STDOUT.should_receive(:puts).and_return("Price cannot be 0 or a negative integer or in decimal format or alphanumeric. \nPlease input appropriate duration in integer\n") #the below two tests fails with syntax error - don't seem that easy to figure out what going wrong #STDOUT.should_receive("Price cannot be 0 or a negative integer or in decimal format or alphanumeric. \nPlease input appropriate duration in integer\n") #STDOUT.should == "Price cannot be 0 or a negative integer or in decimal format or alphanumeric. \nPlease input appropriate duration in integer\n" end Failures: 1) Book#getprice Incorrect input format should return error message asking user to re input Failure/Error: STDOUT.should_receive(:puts).and_return("Price cannot be 0 or a negative integer or in decimal format or alphanumeric. \nPlease input appropriate duration in integer\n") (#<IO:0x00000001c7b298>).puts(any args) expected: 1 time received: 0 times # ./spec/book_spec.rb:40:in `block (3 levels) in <top (required)>' 

I would really appreciate any guidance on how to do this correctly. Thanks.

+4
source share
1 answer

If you run the specification with the --backtrace flag, you will see that an error occurs at https://github.com/rspec/rspec-mocks/blob/v2.13.0/lib/rspec/mocks/method_double.rb#L140 , where rspec -mocks tries to get a single class of an object that has been hatched or mocked. This will work fine with instances of most classes, but there is an error in the specification.

The code sends gets to STDIN , but the specification tries to drown gets on book_obj.books["The Last Samurai"] , which is an int at the time, and you cannot get a singleton from an int :

 $ irb 1.9.3-p392 :001 > class << 1; self; end TypeError: can't define singleton from (irb):1 from /Users/david/.rvm/rubies/ruby-1.9.3-p392/bin/irb:16:in `<main>' 

If I understand correctly, you want to make two changes. First, move the last lines of the script to a separate file that does not load when the specification starts, for example. bin / books (this may require book.rb and then add these lines).

Then remove the line that drowns out gets on book_obj.books["The Last Samurai"] , and then add the line that drowns gets on STDIN , before the line that calls get_prices (which is when the w / STDIN interaction happens):

 STDIN.stub(:gets) { "40" } book_obj.get_prices.should_not be_nil 

This will at least lead to the passage of the specification.

In the general case, code that directly binds w / STDIN and STDOUT is difficult to specify at the object level, because testing tools like rspec, minitest, etc. use STDOUT to represent their information, so you end up with a lot of confusing noise in the shell. I would recommend either changing the design to introduce I / O streams into the book class (which can be STDIN and STDOUT when the script starts, but when the test is doubled when rspec starts) or with a tool like aruba , which is designed to describe interactive shell scripts.

+5
source

Source: https://habr.com/ru/post/1492452/


All Articles