Capturing multi-line console output in Ruby's RSpec testing interface

Photo by Constantin on Unsplash

Capturing multi-line console output in Ruby's RSpec testing interface

How do you capture multiple lines of console output in Ruby? Here is one way to do it. This example comes from writing a test for multiple lines of output from a command-line tic-tac-toe game, where the board should print into the console on 5 different lines.

Solution

  describe "#print_board" do 
    it "prints current state of gameboard" do 
      game.board = ["O", "X", "O", "X", "O", "X", "O", "X", "O"]

      $stdout = StringIO.new
      game.print_board
      $stdout.rewind

      expect($stdout.gets).to eq(" O | X | O \n")
      expect($stdout.gets).to eq("-----------\n")
      expect($stdout.gets).to eq(" X | O | X \n")
      expect($stdout.gets).to eq("-----------\n")
      expect($stdout.gets).to eq(" O | X | O \n")
    end
  end

Ruby-Doc.org Definitions

  • $stdout - The current standard output
  • STDOUT - The standard output. It is the default value for $stdout
  • StringIO - A class that is a "psuedo I/O on a string object."

What does any of this actually mean? Let's help Ruby-Doc.org out a bit

  • $stdout - It's where the output currently goes. It can be reassigned to something else. If you reassign it to a file or a StringIO object, the output is now going there.
  • STDOUT - This is where output goes at the time a Ruby process was launched. It is the default value for $stdout and unless $stdout is reassigned they are essentially the same. This is good because you can reassign $stdout to STDOUT to undo any reassignment you have made.
  • StringIO - You can create a StringIO object from this class and do a number of things with it including write puts to it, or gets strings from it

Solution breakdown

The entire solution again:

  describe "#print_board" do 
    it "prints current state of gameboard" do 
      game.board = ["O", "X", "O", "X", "O", "X", "O", "X", "O"]

      $stdout = StringIO.new
      game.print_board
      $stdout.rewind

      expect($stdout.gets).to eq(" O | X | O \n")
      expect($stdout.gets).to eq("-----------\n")
      expect($stdout.gets).to eq(" X | O | X \n")
      expect($stdout.gets).to eq("-----------\n")
      expect($stdout.gets).to eq(" O | X | O \n")
    end
  end

Code breakdown

$stdout = StringIO.new

Assigns $stdout to a new StringIO object. Any console output is now being saved to a new StringIO object that $stdout points to.

game.print_board

This prints the board to the console.

$stdout.rewind

This calls a .rewind method available from the StringIO class (of which $stdout is pointing to a new instance of). This works basically like a tape, so our StringIO instance has been rewound and is at the beginning of the string input we captured.

      expect($stdout.gets).to eq(" O | X | O \n")
      expect($stdout.gets).to eq("-----------\n")
      expect($stdout.gets).to eq(" X | O | X \n")
      expect($stdout.gets).to eq("-----------\n")
      expect($stdout.gets).to eq(" O | X | O \n")

$stdout.gets simply gets the first line of the output we've captured. Any additional $stdout.gets will just get the next line, and then the one after that. Each string it received included '\n', designating a line break within a string, so the '\n' was included for the test to pass.

Reference