| Tags | #ruby, #testing | Date | |
|---|---|---|---|
| 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.
- 
    Wrap specific puts-ing and gets-ing behavior in code that you own and control. 
- 
    Inject duck-types of the objects on which we can call puts and gets . We call puts and gets implicitly on the objects stored in the $stdout and $stdin variables , respectively. For our tests StringIO is a handy duck-type of both of these objects! 
- 
    Call puts and gets on the, now injected, objects. 
- 
    Use StringIO’s instance methods to write assertions. 
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.