Mock Testing and stub everything

This is a follow up to Advantures in Testing with Mocks. I have been growing dissatisfied with the mockist style of testing recently. The reason is, I was trying to learn it, but it just required me to stub out everything that was called. Not only was it tedious, it also cause the test code to be way too dependent on the implementation code. It got to the point where to write a test, I first have to look at the implementation to see what are the methods that are called. I was all but ready to call it quits on the mockist style.

But, yesterday I found stub_everything, which allowed me to find a way to embrace mocks once again. Basically, stub_everything('my object') is used in place of mock('my object'), which gives you a mock object that will return nil for any message sent to it that it doesn't understand without bombing out. This means that I can escape specifying every method call that has to go on during the tested method invocation.

Also, I realized that mocks are not to be used in all situations, but should rather be used strategically in certain spots. Basically, you want to avoid having your tests be dependent on your code. To give an example, let's say you want to test a controller method that looks up a user from a user ID. Naturally, you would stub out User.find, right? But what if the implementation used User.find_by_id instead? The problem is that there's more than one way to do this, and therefore the implementation is too maliable. Therefore, if it were me, I would not use a mock for a situation like this. Mocks and stubs are to be used only for cases where the method call - or message - is stable, there's only one method available to accomplish the given task, and when theh message is meaningful. For all messages that are not meaningful to be tested for the particular behavior, use stub_everything to ignore them. Here's an example:
describe "audit" do
  controller_name :project
  before :each do
    setup_projects # load data into db
    @user = stub_everything('user')
  end
   
  it "should audit when user is logged in" do
    controller.stub!(:current_user).and_return(@user)
    @user.should_receive(:audit).once
    get :show, {:id => @project.id}, {:user => 1}
  end
end
Here, I justify stubing out the controller's current_user method because that's the only way to get at the current_user that the authentication system provides. As for the expectation for audit, it's also a stable and non-aliased method. But even more importantly, that's exactly what I want to test! This is a case when I want to test the fact that the :audit method was called more so than I want to test its outcome as encoded in the database, because as I change the implementation of audit, its database representation could very well change, heck, audit info might not even be store in the database. As for the rest of the data - how project is looked up - that's retrieved from the database like normal. Real mockist testing people would say that db accesses are slow. There's a point to be made that project look up can also use a stub_everything, but one drawback of that is if the implementation does a multi-level object traversal like: project.name.length, then returning nil for name won't work, and you have to write some extra mocking code. Hmm, perhaps what is needed is a really_stub_everything which instead of returning nils for everything, returns itself instead, then you can really ignore all interactions with this object. Hmm..., that's a thought.

And so, I think that from now on, my mock testing approach - which is not really the mockist way - is to still use fixtures or other db setup templates by default, but only use mocks at select places where it makes sense.

blog comments powered by Disqus