We’ve recently been making use of an alternative to the traditional mock-and-stub pattern common in Ruby: the Test Spy.

Test spies allow you to record method invocations for later verification. Basic usage goes something like this:

describe PostsController do it 'shows the given post on GET show' do post = stub('a post', to_param: '1') Post.stubs(find: post) get :show, id: post.to_param Post.should have_received(:find).with(post.to_param) should render_template(:show) should assign_to(:post).with(post) end end

Compare that with the traditional expectation-based example:

describe PostsController do it 'shows the given post on GET show' do post = stub('a post', to_param: '1') Post.expects(:find).with(post.to_param).returns(post) get :show, id: post.to_param should render_template(:show) should assign_to(:post).with(post) end end

This may seem like a subtle difference, but cleanly separating the test’s phases has real benefits.

The traditional xunit-style test follows four phases:

Setup: create the necessary preconditions for a test Exercise: run the code you’re trying to test Verification: make sure that you got the expected result Teardown: clean up after your test so that it doesn’t interact

When using fast-failing mocks, this process is turned on its head. During the setup, you preemptively “verify” that certain methods are called with certain parameters. If the method is called unexpectedly, it will fail immediately (during the exercise phase). If it doesn’t get called at all, it will fail after the test (during the teardown phase). Besides being counter-intuitive and hard to keep track of, this presents problems when attempting to use the “one testcase per fixture” pattern (common in Shoulda and RSpec suites), or worse, when trying to reuse stubs and behavioral assertions.

Many developers like to test each independent requirement for a piece of behavior individually. Using a mock, that general setup goes like this:

describe PostsController, 'on GET show' do before(:each) do @post = stub('a post', to_param: '1') Post.expects(:find).with(@post.to_param).returns(@post) get :show, id: @post.to_param end it { should render_template(:show) } it { should assign_to(:post).with(@post) } end

If the mock isn’t being used as expected, every example will fail with the same message. In addition, you can’t have an example that specifies “it should find the given user,” because that specification got swallowed by the before block. If you like to write your example descriptions up front, this can be pretty disappointing. Here’s the same example using a test spy:

describe PostsController, 'on GET show' do before(:each) do @post = stub('a post', to_param: '1') Post.stubs(find: @post) get :show, id: @post.to_param end it { should render_template(:show) } it 'should find and assign the given post' do Post.should have_received(:find).with(@post.to_param) should assign_to(:post).with(@post) end end

In this case, the phases are cleanly separated, and you can specify and verify behavior naturally. The two independent requirements can be tested independently, and you’ll get the failures you’d want and expect.

One other problem with mocks is that you can’t share the stub without sharing the built-in verification. That means that every time you reuse a mock, you’re retesting the same behavior. Here’s an example:

describe PostsController do it 'shows a published post on GET show' do post = stub('a post', to_param: '1') post.expects(:published? => true) Post.expects(:find).with(post.to_param).returns(post) get :show, id: post.to_param should render_template(:show) should assign_to(:post).with(post) end end describe '/posts/show' do it 'should display a post' do assigns[:post] = stub('a post', :published? => true, title: 'a title') render '/posts/show' template.should have_tag('h1', assigns[:post].title) end end

Because the mock also sets an expectation, the stubbed post is difficult to reuse in other tests where the expected methods are unimportant. However, using a test spy, you can share stubs and only verify the parts that are important in a particular test:

module PostHelpers def stub_post ( post_attrs = {}) post_attrs = { to_param: '1' , :published? => true , title: 'a title' }. update ( post_attrs ) stub ( 'a post' , post_attrs ) end def stub_post! ( post_attrs = {}) returning stub_post do | post | Post . stubs ( find: post ) end end end describe PostsController do include PostHelpers it 'shows a published post on GET show' do post = stub_post! get :show , id: post . to_param Post . should have_received ( :find ). with ( post . to_param ) post . should have_received ( :published? ) should render_template ( :show ) should assign_to ( :post ). with ( post ) end end describe '/posts/show' do include PostHelpers it 'displays a post' do assigns [ :post ] = stub_post render '/posts/show' template . should have_tag ( 'h1' , assigns [ :post ]. title ) end end

As your test suite grows, the ability to refactor repeated stubs into reusable creation methods will allow you to refactor your production code without correcting mocks and stubs in dozens of files. Also, because the verification phase is separate, common expectations can be pulled into reusable matchers and assertions:

# post.class.should have_received(:find).with(post.to_param) should find(post) # Post.should have_received(:new).with(post_attrs) should build(Post, post_attrs) # Post.should have_received(:new).with(post_attrs) # post.should have_received(:save) should build_and_save(Post, post_attrs)

Test spies are supported by the RSpec Mocks and Bourne test double frameworks. We use RSpec Mocks as our primary default and Bourne when the existing test suite uses Mocha.