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 outputSTDOUT
- 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.