rubyrspecmocking

Testing gets in rspec (user input)


My class has this #run method that so far is just this, to test the testing:

def run
    puts "Enter 'class' to create a new class."
    input = $stdin.gets.chomp
    binding.pry

And in the tests so far I've got

  allow($stdin).to receive(:gets).and_return 'class'
  cli.run

Doing it this way I am able to see, in the pry session, that input has been set to 'class', as intended.

Is there a way to do with without adding $stdin to my call to gets in my method itself? i.e., input = gets.chomp

I've tried allow(cli.run).to receive(:gets).and_return 'class' But then in the pry session, input is equal to the first line of the spec file!


Solution

  • You can avoid this as such:

    def run
      puts "Enter 'class' to create a new class."
      input = gets.chomp
    end
    
    describe 'gets' do 
      it 'belongs to Kernel' do 
        allow_any_instance_of(Kernel).to receive(:gets).and_return('class')
        expect(run).to eq('class')
      end
    end
    

    The method gets actually belongs to the Kernel module. (method(:gets).owner == Kernel). Since Kernel is included in Object and almost all ruby objects inherit from Object this will work.

    Now if run is an instance method scoped in a Class I would recommend scoping the stubbing a bit more such that:

    class Test
      def run
        puts "Enter 'class' to create a new class."
        input = gets.chomp
      end
    end
    
    describe 'gets' do 
      it 'can be stubbed lower than that' do 
        allow_any_instance_of(Test).to receive(:gets).and_return('class')
        expect(Test.new.run).to eq('class')
      end
      # or even 
      it 'or even lower than that' do 
        cli = Test.new
        allow(cli).to receive(:gets).and_return('class')
        expect(cli.run).to eq('class')
      end
    end