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.


  1. I prefer this name over “util_capture”. 

Comments

3 Responses to “Capturing Output from PUTS in Ruby”

  1. Derek Hammer on March 2nd, 2010 7:02 am

    Thanks! This has really helped me out. I really hate saying things like “I can’t test this.”

  2. Albert on May 2nd, 2010 6:33 pm

    Very helpful, thanks! I’m testing a debug function that only “puts” when in a Rack application is in development mode.

  3. Matt on August 12th, 2010 8:53 pm

    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.

Leave a Reply