Not A Mock

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

notahat.com/not_a_mock

A Quick Introduction

Mocking (Not)

When you’re setting up for a spec, you can ask that method calls on an object be recorded:

object.track_methods(:name, :length)

Once your code has run, you can make assertions about what methods were called, what arguments they took, their results, etc.

object.should have_received(:length).without_args.and_returned(42)
object.should have_received(:name).twice

See NotAMock::Matchers for an explanation of the available assertions, plus Object#track_methods and Object#untrack_methods.

Stubbing

Stubbing Methods

You can replace a method on an object with a stub version like this:

object.stub_method(:method => return_value)

Any call to method after this will return return_value without invoking the method’s usual code.

Calls to stub methods are recorded as if you had called track_methods on them, so you can make assertions about them as shown above.

See Object#stub_methods, Object#unstub_methods.

Stubbing Objects

You can also replace an entire object with a stub version like this:

my_object = MyClass.stub_instance(:method_a => return_value, :method_b => return_value, ...)

The returned my_object is a stub instance of MyClass with the given methods defined to provide the corresponding return values.

See Object.stub_instance.

Stubbing ActiveRecord Instances

When you call stub_instance on an ActiveRecord::Base subclass, Not A Mock automatically provides an id method and generates an id for the object.

Yielding To Method Blocks

There are many situation in which a stubbed method will be called with a block attached to it. For example, if you are using the Net-SSH gem, you may have code like this:

Net::SSH.start(@server, @username, :password => @password) do |ssh|
  ssh.exec!(cmd)
end

To correctly stub and test the SSH.start method and the ssh.exec! method, you need to yield an object to the block code, that supports a method called “exec!”.

To do this, you can call “yields(*args)” on a stubbed method. For example, you can can stub the SSH.start method and the exec! method, like this:

@sshstub = Net::SSH::Connection::Session.stub_instance(:exec! => nil)
Net::SSH.stub_method(:start).yields(@sshstub)

When the .exec! method is called from withing the code block of SSH.start, the @sshstub object will be yielded to the block, allowing you to track the method, provide a stubbed block of code to execute, etc.

In situations where you are calling .yields(*args), you can still provide a stub method block to replace the code that is executed when the stub method is called. There are multiple ways of doing this:

Net::SSH.stub_method(:start).yields(@sshstub) { puts 'this is the method stub code for .start' }

or

stub_block = lambda { puts 'this is the method stub code for .start' }
Net::SSH.stub_method(:start, &stub_block).yields(@sshstub)

Both of these syntax forms will provide the exact same functionality - the block that puts the string of information will be executed when you call ‘Net::SSH.start’

Installation

(The following describes using NotAMock with Rails. It should also be possible to use it outside of Rails, but I haven’t tried it.)

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 rspec.info/documentation/rails/install.html 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

Installation via Ruby Gems

At the moment, there is no hosted gem for not_a_mock. However, you can easily build and install the gem from the source directly. To do so, follow these instructions:

1. clone the source code from git://github.com/derickbailey/not_a_mock.git
2. run 'rake jeweler:gemspec' in the root folder of your not_a_mock clone
3. run 'rake jeweler:build' in the root folder of your not_a_mock clone
4. run 'gem install -l pkg/NotAMock-#.#.#.gem' where #.#.# is the version number produced in step 3

After this you will be able to “include ‘not_a_mock’” in your rspec tests, and configure rspec as shown for spec_helper.rb, above.

Contributing

Send bugs, patches, and suggestions to Pete Yandell ([email protected])

Thanks to Derick Bailey, Pat Allan and Steve Hayes for contributing patches.