Pat Maddox, B.D.D.M.F.

RSpec my authority! I just smashed a bug

How I Test Controllers, 2009 Remix

A while back I wrote about how I test

controllers.

I’d like to share my current approach to controller specs, which is

significantly different. I’ve adapted my practice to align with

something I’ve found to be generally true:

Controller specs don’t matter, because controllers don’t matter

The tail end of that statement is the important point. It’s not that

controllers don’t matter, because of course we need them. What I mean

though is that you should have little to no interesting logic in the

controller. Any logic should be

(a) in the model (skinny controller, fat model)

or

(b) in a (mini-)framework

When all the logic is in the model or in framework code, the

controllers become uninteresting bits of glue. Which then makes them

incredibly easy to test - you just poke it and make sure nothing blows

up.

Testing gluey bits

If you’re going to poke something gluey like a controller, you get max

value by hooking up all the attachable parts. This means in a Rails

app, you’re going to want to go through routing, filters, the real

model including associations, the db, and all the way back up to the

rendered view. Welcome to Integration Testing 101.

Using Cucumber to test my controllers

If I’m using cucumber on a project, I use it to test my controllers.

I write acceptance tests and then implement them using webrat to fill

out forms, follow links, etc. Cucumber’s really nice because it

covers every piece I mentioned above. It tests every part of the app

stack except for Javascript, really.

I would have a Cucumber feature that includes:

with the steps implemented as:

That one test lets me know that

  • GET /posts/new renders without errors
  • POST /posts creates a new post and redirects to the index
  • GET /posts renders without errors

Which is enough to make me happy about the coverage, given that my

controllers are brain-dead simple. It’s also less work…I don’t have

to test that the post count increased, or the right template was

rendered, or that it redirected to the right place.

or as DHH might say, “Look at all the things I’m *not* testing!”

One thing I must point out is that I don’t write controller specs if

my controllers are tested through cucumber! I might wrote a couple if

there are edge cases or important alternate paths, but I keep those to

a minimum. I absolutely do not write the typical controller specs,

testing the same stuff over again - that’s a waste of time, both when

you first write it and when you have to maintain the test in two

places later on.

sans-Cucumber alternative: “ping” specs

Maybe you haven’t been bitten by the Cucumber

bug yet. Sometimes I

do projects without Cucumber, because I don’t have an end customer

that I need to communicate with. I use “ping” specs to test my

controllers, so-named because I just ping the controller and see if it

responds (mostly).

Controller specs are more verbose than cucumber features because we’re

working at a slightly lower level thus we must specify different

things.

Here’s a typical controller spec:

I do test a couple other things, such as failure paths (with

create/update, and sometimes destroy) and flash. The example above

shows the essence of my strategy. I want to get a lot of mileage from

my test by hitting it at a high level and using the whole stack. The

only piece missing from here is the routing.

Simple code is awesome, frameworks are awesome

For this to be effective, you have to have wicked tight controller

code and thoroughly tested models. Tight controllers mean that you

can be confident with minimum testing because there’s not much stuff

in the controller that can break. The easiest way to get to that

point is by using a framework such as resource_controller, or rolling

your own simple one. Then be sure your models are well tested,

because that’s where most of the logic and potential for error sits.

This can also be a useful strategy when dealing with absurdly messy

controller code. The goal again is to cast a big net with your test,

but now the reason is so that you’ll be alerted quickly if you break

anything. It might be tough to decipher the problem because you don’t

have good isolation, but at least you have some safety net. The trick

though is to know what code isn’t being tested, or isn’t being tested

enough, so that you can make careful changes. You can find techniques

for analyzing code, identifying and creating seams, and working with

shit code in general in the awesome book Working Effectively with

Legacy Code. I’m also

going to be talking about this, especially as it applies to Rails

projects, at Scotland on Rails in

March.