How to test Ruby methods that involve puts or gets

by Jason Swett,

I recently saw a post on Reddit where the OP asked how to test a method which involved puts and gets. The example the OP posted looked like the following (which I’ve edited very slightly for clarity):

What makes the ask_for_number method challenging to test is a dependency. 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. And instead of necessarily returning a value, this method sometimes simply outputs more text.

How can we give this method the values it needs, and how can we observe the way the method behaves when we give it these values?

Test doubles can help us

The answer is to use test doubles. Like a stunt double, a test double is an object that stands in the place of a “real” object and provides some special behaviors. I’ll provide an example below because I think some code will be clearer than words.

Let’s say that I first want to write a test that says:

  1. When ask_for_number is first called, expect that it prompts us with Input an integer 5 or above
  2. When I respond to that prompt with a value of 6, expect that ask_for_number returns true (because 6 is greater than or equal to 5)

Here are those steps translated into RSpec.

If you play around with this test, you can see how it works. You can see that if you change with('Input an integer 5 or above') to with('Input an orangutan'), the test will fail because it did not receive a call to puts with the argument of 'Input an orangutan'.

The complete test file

When I wrote the full test for this method I ended up with three cases: one for where the user-entered number is greater than 5, one where the user-entered number is 5, and one where the number is less than 5.

In the first two cases, the method should simply return true. In the last case, the method should continue prompting in a loop until the user-entered number is greater than or equal to 5.

For this test I did the following: I simulated the input of 2 (too low), then the input of 3 (too low again), then 6 (high enough). I expected that after 2 we’d receive a call to puts with a value of 'Invalid. Try again:'. Same thing for 3. Finally, I expected the return value to be true after entering 6.

Here’s the full code.

Having said all this, I’ve never written a production test like this. In Rails I’ve found that very seldom, virtually never, do I feel the need to use mocks or stubs or any sort of test double.

  •  
  •  
  •  
  • 2

2 thoughts on “How to test Ruby methods that involve puts or gets

  1. Myron Marston

    This is what I’d do instead:

    https://gist.github.com/myronmarston/d9a699c1c0c74b992ceb1bbe6b4b2c6c

    We talk about this a fair bit in Effective Testing with RSpec 3, but any code that does I/O is going to be far better off being tested with StringIO instead of mocking the _exact method calls_ producing the output. puts "a"; puts "b" and puts "a\nb" behave _identically_, but you can’t write a test that mocks puts that would pass for both implementations, which is a problem. StringIO avoids this; it allows you to test what output was produced, without specifying _how_ the output was produced.

    Going this route improves the design of the Example class, too:

    * It clearly documents its dependencies in its initializer (previously they were implicit)
    * Callers are able to control where the object gets its input from and writes its output to

    Reply
    1. Jason Swett Post author

      Hey Myron, thanks for sharing this. I like your approach a lot better than mine. Would it be okay if I update my post to include your version (with full credit of course)?

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *