7

I'm new to Unit Testing using RSpec and Ruby and I have a question on how to test if my code is using the gets method, but without prompting for user input.

Here is the code I'm trying to test. Nothing crazy here, just a simple one liner.

my_file.rb

My_name = gets

Here's my spec.

require 'stringio'

def capture_name
    $stdin.gets.chomp
end

describe 'capture_name' do
    before do
        $stdin = StringIO.new("John Doe\n")
    end

    after do 
        $stdin = STDIN
    end

    it "should be 'John Doe'" do 
        expect(capture_name).to be == 'John Doe'
        require_relative 'my_file.rb'
    end
end

Now this spec works, but when I run the code it prompts for user input. I don't want it to do that. I want to simply test if the gets method is being called and possibly mock the user input. Not to sure how to achieve this in RSpec. In Python I would utilize unittest.mock is there a similar method in RSpec?

Thanks in advance!

salce
  • 409
  • 1
  • 12
  • 28
  • This is very similar to your question on stdout ( http://stackoverflow.com/questions/37309874/capturing-stdout-in-rspec ) - the code in my file is run as soon as the file is loaded – Frederick Cheung May 19 '16 at 16:38
  • Ah. So by moving `require_relative 'my_file.rb'` line beneath the `expect` it did work. How ever, this still doesn't assure that the `gets` ,method is being called. If I changed the value of `My_name` to anything else besides gets, the test still passes. I need the unit test to fail if `gets` isn't being called. I updated my code. – salce May 19 '16 at 16:45
  • The right way to test this code would be to `expect(My_name).to eq("John Doe\n")` after requiring the file. However, the test will fail due to some interaction between `require_relative` and standard input. Overall, you're going down a bad path in executing code outside classes and trying to test it by requiring it in the test. I'd like to suggest a good path, but it's hard to do it for your example. What is the point of assigning `gets` to a constant in a required file anyway? Perhaps more context would help. – Dave Schweisguth May 19 '16 at 17:26
  • I'm using Unit Test to check if a user is using a function they were just shown. For example, a user just learned how to use the `gets` method and I want them to declare a variable and initialize it with `gets` and submit the answer via an online compiler. I use unit test to check if there correct (Pass/Fail). The only reason the `My_name` variable is a constant is because I need it to be global in order to access it from the spec file. I want to be able to create a Unit Test based on if the user declared a variable and initialized it with the gets method. – salce May 19 '16 at 17:42
  • I suggest requiring the user to write a method and calling that method in your code. (I should have said above that executing code outside methods was going down a bad path, not outside classes.) – Dave Schweisguth May 19 '16 at 19:39
  • I found this article to be helpful. Solution here includes a solution from Myron Marston, the literal author of the book about testing with RSpec3 https://www.codewithjason.com/test-ruby-methods-involve-puts-gets/ – J.R. Bob Dobbs Apr 29 '21 at 18:36

1 Answers1

9

Here's how you could stub gets with your return value.

require 'rspec'

RSpec.describe do
  describe 'capture_name' do
    it 'returns foo as input' do
      allow($stdin).to receive(:gets).and_return('foo')
      name = $stdin.gets

      expect(name).to eq('food')
    end
  end
end

Failures:

  1)   should eq "food"
     Failure/Error: expect(name).to eq('food')

       expected: "food"
            got: "foo"

       (compared using ==)

To test if something is being called (such as a function) you can use expect($stdin).to receive(:gets).with('foo') to ensure it is being called (once) with the right args. The expectation line in this scenario has to go before name = $stdin.gets.

Nabeel
  • 2,272
  • 1
  • 11
  • 14
  • this only shows how to capture a single `gets`, what about multiple sequential `gets`? – notacorn May 27 '21 at 20:13
  • @notacorn you cand pass multiple parameters like: ```allow($stdin).to receive(:gets).and_return('foo', 'bar', 'baz')``` – Fellps Mar 22 '23 at 04:39