Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing STDIN in Ruby

Tags:

People also ask

What is Stdin Ruby?

The $stdin is a global variable that holds a stream for the standard input. It can be used to read input from the console. reading.rb. #!/usr/bin/ruby inp = $stdin.read puts inp. In the above code, we use the read method to read input from the console.

How do you test a method in Ruby?

Most methods can be tested by saying, “When I pass in argument X, I expect return value Y.” This one isn't so straightforward though. This is more like “When the user sees output X and then enters value V, expect subsequent output O.” Instead of accepting arguments, this method gets its value from user input.


I'm currently trying to test a basic method that receives some input from the user (gets) and outputs it (puts). After a bit of research I found a good way to test the standard output stream which is the below:

def capture_standard_output(&block)
  original_stream = $stdout
  $stdout = mock = StringIO.new
  yield
  mock.string.chomp
ensure
  $stdout = original_stream
end

The method I'm testing is the one below, output and input refer to ivars which I initialize in the beginning and point to the equivalent $stdout & $stdin:

def ask_for_mark
  ouput.puts 'What shall I call you today?'
  answer = input.gets.chomp.capitalize
  answer
end

Now I've seen some solutions for STDIN but haven't really understood any of them and I definitely don't want to copy & paste. The only one I got to "work" is the one below, but it's not really working since when I run rspec it pauses and waits for input and by simply pressing enter, it passes:

  it "takes user's name and returns it" do
    output = capture_standard_output { game.ask_for_name }
    expect(output).to eq "What shall I call you today?"
    game.input.stub(:gets) { 'joe' }
    expect(game.ask_for_name).to eq 'Joe'
  end

What would be a good way to test STDIN? I've been staring at a screen for most of the day (not good, I know) so a fresh perspective and some help would be greatly appreciated :-)

Update

For anyone facing similar issues, I followed different (simpler) approaches. First, I would separate the IO operations in their own methods.

In the case of input, in the tests it can be pre-populated with the desired data so when the gets message is send, it will return that data which are separated by the newline character \n like so:

input = StringIO.new("one\ntwo\n")
=> #<StringIO:0x007f88152f3510>
input.gets
=> "one\n"
input.gets
=> "two\n"
input.gets
=> nil

This helps with keep the internals of the method under test, private without coupling the tests to the implementation details.

Another approach is to simply use polymorphism and pass in a Fake or a Spy object that conforms to the same api but instead of making a call to stdin or stdout, it returns canned data instead or in the case of the Spy, it registers the call.

class SpyIO
  def initialize
    was_called? = false
  end
  ...
  def ask_for_name
    # call to stdout would normally take place
    was_called? = true
  end
  ...
end

class FakeIO
  def initialize(data = [some, data])
    @data = data
  end

  def get_user_input
    # call to stdin would normally happen
    @data.shift
  end
  ...
end

There's tradeoffs in each approach but I thought I'd put these here in case anyone was having similar issues or considering options.