Capturing Output from PUTS in Ruby
When writing unit tests for my simplesem interpreter, one test in particular was problematic. In simplesem, the set write instruction prints output to the screen.
// place Hello World! on the 'write' buffer set write, "Hello World!"
Internally, the interpreter is just passing the second parameter, “Hello World!”, to the puts method in Ruby. This makes it difficult to use traditional test/unit assertions to check that the simplesem instruction is working.
I eventually found two solutions for this. The first, suggested by David Stevenson at Pivotal Labs, is to use mocha to check that puts was called on the object.
require 'test/unit' require 'rubygems' require 'mocha' class SimpleSemParserTest < Test::Unit::TestCase def test_set_stmt_write parser = SimpleSemParser.new parser.expects(:puts).with("Hello World!") parser.parse('set write, "Hello World!"').execute end end
This solution is fine for most situations; Mocha will throw an exception if puts is not called. However in my case, it was unsuitable because puts was not being called on the SimpleSemParser object but instead on a Treetop syntax node that I did not easily have access to within the unit test.
I knew that if I could capture the output from the puts method into a variable I would be able write the test using a standard assert_equal. After some googling I discovered that this functionality is built into the ZenTest gem. After rewriting the test looked like this.
require 'test/zentest_assertions' class SimpleSemParserTest < Test::Unit::TestCase def test_set_stmt_write out, err = util_capture do parser = SimpleSemParser.new parser.parse('set write, "Hello World!"').execute end assert_equal "Hello World!\n", out.string end end
This works great. However, should we decided that we do not want to use an external gem, with a little effort we can bypass ZenTest and implement util_capture ourselves.
require 'stringio' module Kernel def capture_stdout out = StringIO.new $stdout = out yield return out ensure $stdout = STDOUT end end
Done! We extended the Kernel module with a method called capture_stdout1. capture_stdout works by redirecting $stdout to an instance of StringIO. StringIO has all of the IO methods, but acts on a string instead of a file. After changing $stdout we yield to let the caller generate output. Once the yield is finished, we return the StringIO instance and add an ensure to guarantee that $stdout is reset to its default value. Adding capture_stdout to the Kernel module has the effect of giving capture_stdout a global scope so that it can be used anywhere.
With the exception that we that we renamed the method to capture_stdout, and are not returning $stderr, the unit test has not changed from the ZenTest version.
class SimpleSemParserTest < Test::Unit::TestCase def test_set_stmt_write out = capture_stdout do parser = SimpleSemParser.new parser.parse('set write, "Hello World!"').execute end assert_equal "Hello World!\n", out.string end end
We end with a clean, very readable way to test Ruby methods that generate output without using any libraries.
-
I prefer this name over “util_capture”. ↩
Comments
3 Responses to “Capturing Output from PUTS in Ruby”
Leave a Reply

Thanks! This has really helped me out. I really hate saying things like “I can’t test this.”
Very helpful, thanks! I’m testing a debug function that only “puts” when in a Rack application is in development mode.
Thank you! Your elegant capture_stdout method was exactly what I needed for testing the “trace” feature of a little embedded interpreter I’m working on.