Testing Controller Helpers

Posted June 15, 2007

As you may recall from my post yesterday, I’m working on a revision of the engine behind this blog. Most of the legwork is already done; I’ve pored over every line of code, making sure it’s as elegant and accurate as it can be. I’ve fixed the logo, which apparently renders wonkily on some computers. I’ve tightened up the UI for inline editing of comments and such. All good stuff. No new features yet (that comes next time), but the innards are much more beautiful.

So why not just upload it and sing glory? Tests. When I first wrote this thing, I didn’t get around to writing any tests to go with it. This is, as anyone involved in the development of stuff like this will tell you, a Bad Thing. So as part of the revision, I’m painstakingly testing every last action1 and helper method, which is taking some time.

I’m not actually using Rails’ default testing framework, which is based on the standard-library test/unit. Rather, I’m using RSpec, Ruby’s BDD testing framework2, along with its Rails extension and the Mocha mocking framework. I’m not actually fond of BDD, but RSpec still has numerous advantages. I don’t want to get into either of these issues right now, but you can count on seeing them in later entries.

I just finished writing all the tests for my controllers. This presented a bit of a hassle. You see, I didn’t just want to test each of the actions. Although this seems very nice and black-box on the surface, there’s all this internal work going on that the tests are ignoring. Moreover, this internal work is my code, and thus it’s my responsibility to ensure that it’s working properly. Black box testing just doesn’t cover this; I try to make my helpers as generic and reusable as possible, so it’s often the case that the actions don’t fully excercise the helpers. Thus, testing the action will in turn fail to fully test the helper.

Luckily, RSpec encourages mocking and stubbing3 out these methods so it’s possible to test an action without getting mucked up in calling helper methods. If I can assume the methods are individually well-tested, then all I have to test for the action is that it calls them properly, which is easy as pie with Mocha.

Here we run into the problem that I’ve been facing. Simply put, whether using test/unit or RSpec, it’s not easy to test controller helpers. In fact, it’s very annoying. Both testing frameworks are deeply wedded to the model of testing individual HTTP requests. Think about it: how many test cases have you seen without get, post, put, or delete called?

This means that if you want to test a helper method, it can be a huge challenge. If you’re very lucky, you can just call controller.#{helper_method} and test that as you would any other method. If you’re a bit less (but still significantly) lucky, you can call get or whatever to initialize the data you need, and work from there.

Most of the time though, it doesn’t work out that nicely. Controllers just have too much baggage that has to be dealt with. Anything that touches response, before_ or after_filter, session, cookies, url_for, render, redirect_to, or innumerable other request-bound methods are contaminated. They just won’t work unless there’s an active request being processed.

The only reasonable solution seems to be stubbing, stubbing, and stubbing some more. This gets really ugly and hackish, and is of course only possible if you’re using Mocha and/or RSpec.

How can this be fixed? I think the only way is to realize that, in order to test controller helpers separately from the actions that call them, their tests need to be run in a separate context from that in which actions are tested. This context needs to be able to do a few things:

  • Provide lots of nice default stubs and faux-objects for the request, the response, and associated methods.
  • Provide a way of configuring input, so parameters, HTTP request type, etc. can be changed. This could be done by simply allowing these things to be easily stubbed.
  • Optionally run independantly of any specific controller, so more generic helpers like those one would put in ApplicationController can be tested without interference.

With that, I think a lot of the headache would be gone. For now, I guess I’ll just stub away.

1 Well, not really. I’ll get to that at some point.

2 RSpec calls tests “specs,” but I prefer “tests” for various reasons.

3 For those uninitiate in the arcana of BDD, “stubbing” a method is telling that method’s object not to actually run the method when it’s called, but rather just to return a given value. “Mocking” is similar, but it also establishes an expectation that the method was called with certain parameters. In non-BDD terms, a mock automatically sets up a test to make sure that the method was called, while a stub doesn’t care whether or not it was called.

Alf Mikula said January 27, 2009:

I’m having the same problem! I have a controller with a “show” method, so I ended up doing this in a before(:each) block:

controller.stub!(:show)
get :show

This way, the request gets set up and parameters get parsed…everything gets set up for the request as normal, but the controller method is not executed. Then you can call your helper method on the controller class and it works fine.

Make your comments snazzy with Textile!