stubbing 'gets' in ruby multiple times

520 views Asked by At

I have a simple question asking Ruby script:

def ask question 
  while true
    puts question
    reply = gets.chomp.downcase

    return true if reply == 'yes' 
    return false if reply == 'no'

    puts 'Please answer "yes" or "no".' 
  end
end

puts(ask('Do you like eating tacos?'))

I'm testing it like this

describe 'ask' do
  before do
    stub(:puts).with('anything')
    stub(:puts).with('Please answer "yes" or "no".')
    stub(:gets).and_return 'yes'
  end
  it 'returns true when you say yes' do
    expect(ask('anything')).to be true
  end
  it 'returns false when you say no' do
    stub(:gets).and_return 'no'
    expect(ask('anything')).to be false
  end
end

I had previously been trying to use RSpec 3 syntax with code like

allow(STDIN).to receive(:gets).and_return 'yes'
allow(Kernel).to receive(:gets).and_return 'yes'
allow(IO).to receive(:gets).and_return 'yes'

and other variations but none of these worked, just giving me errors like:

undefined method `chomp' for nil:NilClass

I had better luck with the RSpec 2 syntax, so I enabled that and got the above working, almost. The problem is the line: puts(ask('Do you like eating tacos?')). If that is commented out, all is fine, however with it present I was getting this error:

Errno::ENOENT: No such file or directory @ rb_sysopen

and then now

undefined methodchomp' for nil:NilClass`

So it seems I can stub 'gets' in a method that's called from RSpec, but not if a method using it is called from the ruby file that RSpec is testing.

Any ideas?

1

There are 1 answers

0
Sam Joseph On BEST ANSWER

Aha, I think I found the solution!

The trick appears to be allow_any_instance_of(Kernel).to receive(:gets).and_return 'yes' and calling that in a before block before the application code is pulled in - like so:

describe 'ask' do
  before do
    stub(:puts).with('anything')
    stub(:puts).with('Please answer "yes" or "no".')
    stub(:gets).and_return 'yes'
    allow_any_instance_of(Kernel).to receive(:gets).and_return 'yes'
    require './lib/ask.rb'
  end
  it 'returns true when you say yes' do
    expect(ask('anything')).to be true
  end
  it 'returns false when you say no' do
    allow_any_instance_of(Kernel).to receive(:gets).and_return 'no'
    expect(ask('anything')).to be false
  end
end