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:
- The actions taken by the controller are internal, not based on roles it should interact with
- 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:
- What is the proper way to test rails requests with mocking?
- 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)?
- 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.
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.
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?
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.
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
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.