One of the great advantages of using mock objects to test and specify your objects is that you concentrate solely on the thing you are testing.
If you weren’t using mocks to tests that a controller re-shows the “new” form if given an invalid object, you would do post :create, :model => { ... } where … is a set of fields that are invalid. This means that, when writing your spec, you have to remember what it is that makes that model invalid. This also means that, every time you change that model, and its rules for validity, you potentially have to amend the controller test as well. In other words, you are not actually testing the controller in isolation, you are also testing the model.
Using mocks lets you write the following:
@model = mock Model
Model.should_receive(:new).and_return(@model)
@model.should_receive(:save).and_return(false)
post :create, :model => { :some => :fields }
response.should render_template('/models/new')
In other words, it doesn’t matter what parameters you send to create. The Model (class) will return you a mock instance of model, and the mock instance will return false on save (you can actually get save! to raise an ActiveRecord::RecordInvalid – but I’ve had some difficulties with that). In other words, the real model is no longer part of the test and you are concentrating on the behaviour of the controller alone.
This concentrating on what is important is a vital advantage when using mocks.
But how does this work on testing models?
I wanted to test that a default value was copied from one field to another under certain circumstances. So I set up a method, called set_default_value and wrote some specs to ensure that it was working. Then I wrote the following:
it "should set the default value before validation" do
@model = Model.new
@model.should_receive(:set_default_value)
@model.valid?
end
This failed, as it should do. Then I set a before_validation :set_default_value on the model and the spec passed. Each spec concentrates purely on what is important – a couple to show that set_default_value works under different circumstances, and one to show that it is called when it is supposed to be.
What about testing a :dependent => :destroy association?
Unfortunately, that can’t be done without saving at least one of the objects in the association (which means knowing enough about it to make it valid). But, as David Chelimsky (Mister RSpec) points out on the RSpec mailing list, you can do it using mock objects for part of the association. Which was a relief as my “child” object was complex with a whole set of interdependencies of its own.