If you run the spec with the --backtrace
flag, you'll see that the error is raised on https://github.com/rspec/rspec-mocks/blob/v2.13.0/lib/rspec/mocks/method_double.rb#L140, where rspec-mocks is trying to get the singleton class of the object being stubbed or mocked. This would work fine with instances of most classes, but there is an error in the spec.
The code sends gets
to STDIN
, but the spec is trying to stub gets
on book_obj.books["The Last Samurai"]
, which is an int
at the time, and you can't 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 doesn't get loaded when you run the spec e.g. bin/books (it can require book.rb and then add those lines).
Next, remove the line that stubs gets
on book_obj.books["The Last Samurai"]
and add a line that stubs gets
on STDIN
instead, before the line that invokes get_prices
(which is when the interaction w/ STDIN
happens):
STDIN.stub(:gets) { "40" }
book_obj.get_prices.should_not be_nil
That will at least get your spec passing.
In general, code that deals w/ STDIN
and STDOUT
directly is hard to spec at the object level because testing tools like rspec, minitest, etc, all use STDOUT
to present their information, so you end up with a lot of confusing noise in the shell. I'd recommend either changing the design to inject input/output streams to the book class (which can be STDIN
and STDOUT
when you run the script, but test doubles when you run rspec), or use a tool like aruba, which is designed to spec interactive shell scripts.