Testing Ruby’s `gets` & `puts`

Using Dependency Injection, Sensible Defaults, & Duck-Types

The Problem.

You want to TDD some behavior that interacts with the command line:

puts "Would you like to continue? [yN]"
answer = gets.chomp.downcase

But testing this idea is difficult; when your tests run these lines of code, they can cause your tests to hang or send unwanted output to your console.

A Solution.

An Example Test.

RSpec.describe ConsoleInterface do

  describe '#ask_question' do
    it 'sends a prompt question to output' do
      output = StringIO.new
      console_interface = ConsoleInterface.new(output: output)

      console_interface.ask_question

      expect(output.string).to include("continue?")
    end
  end

  describe '#answer' do
    it 'returns a formatted string received from input' do
      input = StringIO.new("iNPut\n")
      console_interface = ConsoleInterface.new(input: input)

      expect(console_interface.answer).to eq("input")
    end
  end

end

An Example Implementation.

class ConsoleInterface

  def initialize(input: $stdin, output: $stdout)
    @input = input
    @output = output
  end

  def ask_question
    @output.puts "Would you like to continue? [yN]"
  end

  def answer
    @input.gets.chomp.downcase
  end

end

Why I Like This.

The power of Ruby is that if it quacks like a duck, it must be a duck! By combining this freedom with dependency injection, we can quickly grab control over our dependency on what we often take for granted: the command line.

Also, you might be excited to learn that you’ve just used a test double! They can often be seen as big scary controversial things that require you to pull in heavy libraries, but because of duck-typing, using a test-double can be as simple as injecting a built-in object that we have more control over.

· ruby, testing