Rails Test Prescriptions Blog

Keeping Your Application Healthy Since 2008

RSpec and Mock Design Question

Here’s a little RSpec design question.

As I’ve probably mentioned in various spots, I don’t naturally take to the RSpec massively-mocked style of testing. However, I’m currently on a Rails project that is using that style — unit tests don’t touch the database, functional tests don’t touch the models. It seems to be working for them, they certainly seem to have stuck with it over the course of this rather complex application.

Anyway, today, my pair and I added a new before_filter to the layout of our application, where it gets called by every controller test in the system. This filter calls some user methods to put some dynamic user data on the screen.

Suddenly, we have failing tests all over the place, the vast majority of them related to mocks, mostly having to do with mocks or stubs that haven’t defined the method called in the filter and therefore thrown a mock expectation exception or, less frequently, a mock that does call these methods, but has an expectation that they will only be called once.

Wading through all these things is kind of daunting, and it’s not doing much to raise my general opinion of mock-heavy testing. That aside — I’m wondering how this is supposed to work. That is, I’m curious as to how a true RSpec expert would answer the following questions:

  1. How would this issue — adding a new method call against an existing family of mocks — be handled in a perfectly designed and maintained RSpec test structure? What is the ideal here?

  2. Given a system in progress that, while not bad, has had a lot of different people working on it in their own style, what’s the ideal way to proceed from here

Just wondering.

2 responses to “RSpec and Mock Design Question

  1. Brian Guthrie November 5, 2009 at 11:37 am

    I don’t know if I’d call myself a “true RSpec expert”, but I’ve delivered several projects in it, and I hear where you’re coming from. Controllers are a frequent source of mock break pain because they touch so many things; change your update method from a save to a save!, and suddenly all hell breaks loose.

    Two suggestions:
    – For what it’s worth, and I know it’s old advice, but the more you have your controllers try to do the suckier it gets. If you need to render additional content, offload that into a dedicated view-rendering object, Presenter-style, and mock that as necessary. If it’s a matter of data munging on an incoming POST, offload more of that into your model (or get a PORO to quack like a model, ActiveModel-style) and let it handle dirty params (within reason). Because those boundaries are more clearly defined and change less often you should hopefully experience less pain, and those classes can be tested more granularly.
    – In your specs, offload generalized mock creation into dedicated helper methods or setup blocks. Yes, I know setup blocks are evil, but sometimes the dramatic improvement in test maintainability is worth the tradeoff.

  2. Ryan Briones November 5, 2009 at 9:38 pm

    generally when using a before filter that’s going to be used a lot, i’ll stick that setup and examples specific to that in a shared_examples_for block. for instance, authentication.


    describe SomeController do
    before(:each) do
    # exisiting mock setup
    end
    # add to mocks, placement after before is key
    it_should_behave_like "a before filter thingie"
    it "blah"
    end


    shared_examples_for "an action that requires a user" do
    before(:each) do
    # setup
    end
    it "should redirect to login path if user is not logged in"
    # etc…
    end


    require File.expand_path('/path/to/authenication_helper')
    describe SomeController do
    it_should_behave_like "an action that requires a user"
    describe "when listing somes" do
    it "blah"
    end
    end

    here, authentication helper an some controller spec is how i would do a project like this from the start.

    the other file in the gist shows how you can use shared examples placed after an existing before block that sets up mocks to add stub methods or expectations. that part is not pretty, but works. :/

Leave a reply to Brian Guthrie Cancel reply