53

I am trying to build a spec for this statement. It is easy with 'puts'

print "'#{@file}' doesn't exist: Create Empty File (y/n)?"

2 Answers 2

103

RSpec 3.0+

RSpec 3.0 added a new output matcher for this purpose:

expect { my_method }.to output("my message").to_stdout
expect { my_method }.to output("my error").to_stderr

Minitest

Minitest also has something called capture_io:

out, err = capture_io do
  my_method
end

assert_equals "my message", out
assert_equals "my error", err

RSpec < 3.0 (and others)

For RSpec < 3.0 and other frameworks, you can use the following helper. This will allow you to capture whatever is sent to stdout and stderr, respectively:

require 'stringio'

def capture_stdout(&blk)
  old = $stdout
  $stdout = fake = StringIO.new
  blk.call
  fake.string
ensure
  $stdout = old
end

def capture_stderr(&blk)
  old = $stderr
  $stderr = fake = StringIO.new
  blk.call
  fake.string
ensure
  $stderr = old
end

Now, when you have a method that should print something to the console

def my_method
  # ...
  print "my message"
end

you can write a spec like this:

it 'should print "my message"' do
  printed = capture_stdout do
    my_method # do your actual method call
  end

  printed.should eq("my message")
end
Sign up to request clarification or add additional context in comments.

9 Comments

Where should the def capture_stdout &block code go? In the spec file?
You can put it in a separate spec helper file and then include that file whenever you need it
@p11y: It works like a charm, thanks. One small correction though, the it block should have a do, I couldn't edit the answer since the edit must be atleast 6 characters
Is that &blk a proc?
@Jwan622 yes, &blk is a proc that contains the computations in the do ..end block passed to the method.
|
3

If your goal is only to be able to test this method, I would do it like this:

class Executable
  def initialize(outstream, instream, file)
    @outstream, @instream, @file = outstream, instream, file
  end

  def prompt_create_file
    @outstream.print "'#{@file}' doesn't exist: Create Empty File (y/n)?"
  end
end


# when executing for real, you would do something like
# Executable.new $stdout, $stdin, ARGV[0]

# when testing, you would do
describe 'Executable' do
  before { @input = '' }
  let(:instream)   { StringIO.new @input }
  let(:outstream)  { StringIO.new }
  let(:filename)   { File.expand_path '../testfile', __FILE__ }
  let(:executable) { Executable.new outstream, instream, filename }

  specify 'prompt_create_file prompts the user to create a new file' do
    executable.prompt_create_file
    outstream.string.should include "Create Empty File (y/n)"
  end
end

However, I want to point out that I would not test a method like this directly. Instead, I'd test the code that uses it. I was talking with a potential apprentice yesterday, and he was doing something very similar, so I sat down with him, and we reimplemented a portion of the class, you can see that here.

I also have a blog that talks about this kind of thing.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.