Question

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.

Was it helpful?

Solution

You can simply stub STDIN:

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?"
  allow(STDIN).to receive(:gets) { 'joe' }
  expect(game.ask_for_name).to eq 'Joe'
end

Actually, you can do the same with STDOUT, without needing to change $stdout:

it "takes user's name and returns it" do
  expect(STDOUT).to receive(:puts).with("What shall I call you today?")
  allow(STDIN).to receive(:gets) { 'joe' }
  expect(game.ask_for_name).to eq 'Joe'
end

OTHER TIPS

Well I can't really be sure, but one problem I had (still have) when working with $stdin / $stdout / StringIO is making sure to run #rewind on them. Are you doing that?

input = StringIO.new
input.puts "joe"
input.rewind
input.gets #=> "joe\n"
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top