3

I have a simple function that I'd like to test (perhaps mostly to appease simplecov). The function is:

module Utils
  extend self

  def blather(msg)
    msg = "=== " + msg
    STDERR.puts(msg)
    Rails.logger.debug(msg)
  end

end

The RSpec documentation for stubbing says that:

Messages can be stubbed on any class, including those in Ruby's core library.

But the following:

# file: spec/lib/utils_spec.rb
require 'spec_helper'
describe Utils do
  context "blather" do
    it "should print to STDERR" do
      STDERR.any_instance.should_receive(:puts).with("=== zoo")    
      Utils.blather("zoo")
    end
  end
end

... I get an error

undefined method `any_instance' for #<IO:<STDERR>>

Putting aside questions as to whether this test makes any sense, is it possible to stub STDERR (the IO class)? Is this failing because it's a class method? Or is there a more sensible strategy for this kind of test?

fearless_fool
  • 33,645
  • 23
  • 135
  • 217

3 Answers3

6

Firstly, you should typically use $stderr rather than STDERR.

module Utils
  extend self

  def blather(msg)
    msg = "=== " + msg
    $stderr.puts(msg)
    Rails.logger.debug(msg)
  end

end

To answer your question, you can do the following in RSpec:

describe Utils do
  context "blather" do
    it "should print to stderr" do
      $stderr.should_receive(:puts).with("=== zoo")
      Utils.blather("zoo")
    end
  end
end

You just stub the method via $stderr.should_receive. Because $stderr is a normal object, you can stub methods on it like normal as well as set up expectations on it.

yfeldblum
  • 65,165
  • 12
  • 129
  • 169
  • Yep - see my "Duh" answer below. – fearless_fool Apr 11 '12 at 17:28
  • P.S.: Thanks for the tip about preferring $stderr over STDERR -- (http://stackoverflow.com/questions/4279604/what-is-the-difference-between-stdin-and-stdin-in-ruby explains that $stderr can be reassigned while STDERR cannot). – fearless_fool Apr 11 '12 at 17:29
3

There are many ways to write to stderr, so it's usually better to test the resulting string instead of requiring puts was called once with specific arguments.

As long as your function uses $stderr (which is intended to changable) rather than STDERR const, you can use rspec's beautiful output matcher:

expect { blather("zoo") }.to output(/=== zoo/).to_stderr

If you need more control, you could replace $stderr (and maybe stub_const "STDERR") with a StringIO instance, and make assertions about its .string later... (This is what output matcher does internally.)

Beni Cherniavsky-Paskin
  • 9,483
  • 2
  • 50
  • 58
1

Duh. STDIO is not a class -- it is an instance of IO, so changing:

STDERR.any_instance.should_receive(:puts).with("=== zoo")

to

STDERR.should_receive(:puts).with("=== zoo")

makes the test pass.

fearless_fool
  • 33,645
  • 23
  • 135
  • 217