How I Test Controllers

I’ve been trying to improve the specs I write lately. My method before was mostly copying and pasting from my past projects. Somewhere way back I adapted them from an early version of Michael Hartl’s Rails Tutorial, making various modifications over the years.

Recently I decided to scrap my specs and start over from scratch. Since my controller specs were particularly ugly, I decided I’d start with them.

I wanted to share what I learned, so here goes. In true test-driven fashion, we’ll write a little one-controller app, specs first. Our app will be a list of favorite books. We’ll start with the most basic skeleton:

 

Because this app is so simple, there will be nothing at all interesting in our model, and thus nothing to test. We’ll trust ActiveRecord to do its job since that code is already tested ;) Here’s our model:

 

Now let’s start with index. What do we expect an index action to do?

  • We need to get an array of all books from ActiveRecord via the model.
  • We need to assign that array to a variable for the view
  • We need to render the index template
  • We need to indicate to the browser that the request was successful (HTTP status 200)
  • We don’t need to set any flash messages
  • There’s really only one path to test. The only abnormal instance would be an empty array of books, but the controller doesn’t really know or care whether this happens – just the model and the view.

In the past I would have done a Factory Girl create in a before block to test getting the array. But the controller isn’t really responsible for getting the ActiveRecord object, only asking the the model for it and passing the result on to the view. Besides, we should probably write integration specs to make sure everything works together, so we’re going to test that anyway. That’s why these days I like to use mocks in testing my controllers.

Let’s go ahead and create a Factory Girl factory anyway. We can use attributes_for  as a convenient way to get our params hash, and the factory will be useful later for our integration specs and possibly model if we add anything interesting like validations.

 

Time to write our first failing spec

 

And we get our failure: Book didn’t receive all. Let’s modify the index action in the controller:

 

And it passes. Now let’s write our first real spec.

 

Of course it fails, because while we called all on Book to get our first expectation to pass, we didn’t assign it to a variable for the view. Let’s fix that.

 

And it passes. On to our next spec. It should respond with success. Because this is the normal response for rails, let’s set ourselves up for failure.

 

And write our spec:

 

Failure. Let’s fix it and set up our next failure.

 

Success.

 

Failure.

 

Success.

 

Failure.

 

One could argue that it seems excessive to write code to make such simple specs fail, and I’ll readily admit I don’t always do this. But this is what the rhythm of TDD should feel like, and it’s not completely safe to trust specs you haven’t seen fail.

#show, #new, and #edit will be very similar – there’s only one path and they simply render a view. I’ll include the full code at the end.

The next interesting challenge is #create. Let’s take a crack at that.

What do we expect a create action to do?

  • We need to instantiate a new Book with the params we’re given
  • We need to assign the object to a variable for the view in case we need to display it
  • We need to ask the book to persist itself
  • If the save reports success, we want to:
    • Set a flash message indicating success
    • Redirect to index
    • Because it’s a redirect, index is responsible for anything that happens after that. We’ve already tested that.
  • If the save fails, we want to:
    • Render the new template
    • Indicate to the browser that the request was successful (HTTP status 200) – despite the fact that the save was not
    • We don’t need to display any flash messages

 

Failure – Book didn’t receive new

 

Success. Speaking of which, it’s time to branch our specs for success vs failure.

 

It fails.

 

It passes.

 

It fails.

 

It passes.

 

It fails.

 

It passes. The failure path behaves virtually identically to the new action. We’ll look at a way to exploit this and other similarities to DRY up our specs in a follow-up post on shared examples. For now, notice that book is a local variable. This allows us to call save while still setting up our spec to check whether the book is assigned to an instance variable for failure.

Here is the final create action and corresponding spec:

 

 

So what are the takeaways?

  • Write specs that test the thing you’re testing. Use mocks. This makes tests easier to write, easier to read, and super fast. On my machine, 29 specs run in less than two tenths of a second without Spork.
  • Describe the behavior of the thing you’re testing. Do this before you write your specs. When you’re done, your specs are documentation. Try rspec spec --format documentation
  • Follow the rhythm of TDD: red, green, refactor. For easy stuff like this, refactoring might not be necessary.

It should go without saying, but these are just guidelines. Fast tests are a means, not an end. There are cases where mocking becomes awkward. TDD is a tool, not a dogma.

I’ve posted the finished code on GitHub.

I hope this is helpful to others, and I welcome feedback.

About Alexander

Alexander has been programming for a very long time. When he met Ruby, it was love at first sight.
This entry was posted in Uncategorized and tagged , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *