Not A Mock

A cleaner and DRYer alternative to mocking and stubbing with RSpec.

Not A Mock extends RSpec so you can stub out methods (or entire objects), then make assertions about the calls to those methods after the fact.

Example

I have a Rails app that has a Message model that stores and sends SMS messages. I'd like to test it, but I don't want it delivering the test messages.

The Not A Mock way:

describe Message, "deliver method" do
  
  before do
    # Replace the send_message method on our singleton SMSGateway
    # with a version that does nothing and returns true.
    SMSGateway.instance.stub_method(:send_message => true)
    
    @message = messages(:sample_message)
    @message.deliver
  end
  
  it "should pass the message to the gateway" do
    # Make sure the send_message method was called with the right arguments.
    SMSGateway.instance.should have_received(:send_message).with(@message.recipient, @message.body)
  end
  
  it "should mark the message as sent" do
    @message.status.should == "sent"
  end
  
end

The old-fashioned way, using RSpec's standard stubbing and mocking:

describe Message, "deliver method" do
  
  before do
    @message = messages(:sample_message)
  end
  
  it "should pass the message to the gateway" do
    # Create a mock object to replace the gateway.
    @gateway = mock("SMSGateway")
    # Assert in advance what methods should be called on the mock object.
    @gateway.should_receive(:send_message).with(@message.recipient, @message.body)
    # Make the gateway class return the mock object as the singleton.
    SMSGateway.stub!(:instance).and_return(@gateway)
    @message.deliver
  end
  
  it "should mark the message as sent" do
    # Replace the send_message method on our singleton SMSGateway
    # with a version that does nothing and returns true.
    SMSGateway.instance.stub!(:send_message).and_return(true)
    @message.deliver
    @message.status.should == "sent"
  end
  
end

See the RDoc documentation for more details.

Installation

(The following describes using NotAMock with Rails. It should also work outside of Rails, but it's untested.)

First, install the rspec and rspec_on_rails plugins:

ruby script/plugin install http://rspec.rubyforge.org/svn/tags/CURRENT/rspec
ruby script/plugin install http://rspec.rubyforge.org/svn/tags/CURRENT/rspec_on_rails
ruby script/generate rspec

(See the RSpec docs for more details.)

Second, install the not_a_mock plugin:

ruby script/plugin install git://github.com/notahat/not_a_mock.git

Finally, add the following to your project‘s spec/spec_helper.rb:

config.mock_with NotAMock::RspecMockFrameworkAdapter