Check out my new company

MeetSpace Logo MeetSpace: Video conferencing for distributed teams

Mocking on Rails

by on

Gregory Moeck's awesome post Stubbing is Not Enough got my brain back on the subject of mocking. Readers of this blog may note that I had quite a rant against mocking almost a year ago and Gregory posted a response. I think the result of that post and the discussion that ensued was not that mocking and/or stubbing were bad practices, but that when they are applied inappropriately they can quickly deteriorate the tests and the design of an application.

After reading Gregory's article, I wanted to revisit the state of mocking in Rails applications. One of the things I noticed in his article was that he was addressing some ruby classes and their interactions. He enforced the OO concepts of message passing and how mocks are better than stubs at testing very OO code. While I really like his solution, something was nagging at me: how can I do this in Rails?

For example, here is the stock rails scaffold controller and functional test:

class PostsController < ApplicationController
# GET /posts
# GET /posts.json
def index
@posts = Post.all

respond_to do |format|
format.html # index.html.erb
format.json { render :json => @posts }
end
end
end

class PostsControllerTest < ActionController::TestCase
setup do
@post = posts(:one)
end

test "should get index" do
get :index
assert_response :success
assert_not_nil assigns(:posts)
end
end

What can I mock? In Gregory Moeck's example, the ticket reservation object was passed in to the ticket machine interface (dependency injection) so we could easily mock the ticket reservation role and assert that the ticket machine interface interacted with it properly. He also intentionally doesn't touch the @current_display variable because it is internal to the system.

In our Rails controller and functional test, we can observe the following:

  1. The actions taken by the controller are internal, not based on roles it should interact with
  2. The functional test is testing internal state (assigns) and not messages passed by the controller

My gut at this point says I'm on an integration boundary between the user and my internal system. So that means I would have an integration test on the controller. But still, the signature of a rails controller to call activerecord and render doesn't seem to lend itself to encapsulation and mocking.

At this point I attempted to write what I thought was a change to the way controllers work using dependency injection and object composition, but I failed at it. So I'm leaving this post with some open questions:

  1. What is the proper way to test rails requests with mocking?
  2. What is the proper way to do an integration test, and how deep should it go (i.e. beyond the point that you have covered with unit tests)?
  3. Are there other web frameworks out there with great encapsulation as a best-practice?

Super extra bonus points if you post links to open source projects that have a test suite that actually do these things well. Thanks.

Comments

Greg Moeck
A good number of people have asked me that question since I wrote the post. My general response is two fold.

First, if I'm just doing CRUD reading and writing, I don't really feel the need to have unit tests for the project, so long as I have end-to-end acceptance tests. The logic in Rails is simple enough that a computer could write it, so I don't really feel a high degree of risk there.

However if I'm dealing with a complex domain then I tend to separate out my domain layer from Rails, and treat my controllers as ports (from Alistair Cockburn's ports and adapters architecture) into and out of the web. I will have them talk to an adapter within my domain, which is all well encapsulated and heavily unit tested. I generally don't unit test the controller or the view layer , and just let my end-to-end acceptance tests ensure that everything is plugged together correctly. However if something is particularly hairy I will cover the rails part in an integration test.

The basic approach is similar to what Eric Evans calls the "Anticorruption Layer" in Domain Driven Design (page. 366).

I'm working on a sample application for an auction house which you can at see at https://github.com/gmoeck/auction_house. There isn't much to see in the actual domain layer yet, but it will give you a general idea.
Nick Gauthier
awesome, thank you for chiming in. One thing I've been mulling over is how much of the variability in the controller's form=>params=>domain model do you cover w/ acceptance tests?

If a form has a bunch of params, some options (radio buttons) some non-required some required, you have to make sure the fields are wired up some how to get passed in to your domain model. Do you test all the paths?
Greg Moeck
I personally tend to try and leave that stuff to the adapter to decide what to do with and think of the controller as more of just the way that I receive and send data. That's why i don't generally don't feel the need to unit test them because the controller is just getting all the data relavent to the request and handing it off to the domain to decide what it means. My adapter object will generally then read the parameters and send messages into the domain in the domain's language according to whatever the parameters mean. This isolates my actual domain objects from changes in the system and allows me to plug in the same domain into another delivery mechanism so long as I write an adapter for it.

The more complex side is when the controller then queries the "view side" of the domain to get a response object, which then passes that data to the view, or renders an error or something. This is where my integration sort of tests will sometimes come in if I want to unit test that logic because it is getting complex.

I certainly don't feel like I have this all figured out, and I'm personally excited that people are starting to think more along these lines because I feel like the Rails side of the equation is going to clean up a bit in the immediate future. Sometimes I do feel like Rails is a bit of an overkill though since I can use Rack and accomplish most of what I'm wanting to do with the "web framework" part of my application.
Nick Gauthier
cool.

So generally your domain objects play into the controller like:

if obj.create(params); head :ok; else; render :json => obj.errors

I've been looking more into goliath and it seems to strike a nice balance of rack-like directness (no magic) and also basic HTTP API niceties like content encoding.

Personally, I've been on an "acceptance test everything no unit tests" kick. It has proven to:

1) make very reliable software
2) make very slow test suites
Sam Goldman
This is perhaps an unfairly literal response to a contrived example, but if you are writing controllers like that, another option would be to use a library like inherited_resources, which is already well-tested, and just test the happy path in an integration.
Nick Gauthier
well yeah if you're using the stock scaffold you don't need to test it either, since scaffolds are well tested.

Mostly just interested because this is the standard way of writing a controller action.

As an aside, I'm not a big fan of inherited resources. I prefer scaffolding. Tracking down bugs and determining behavior w/ inherited resources is a pain. I'd rather type it out.
blog comments powered by Disqus