Poor controllers. As Rails developers we keep them skinny (which is a good thing) and often don’t give them due attention in our tests (which is a bad thing; more on that in a moment). As you continue to improve your application’s test coverage, though, controllers are the next logical chunk of code to tackle.
If you’re new to RSpec or Rails testing in general, I recommend reading through the previous posts in this series first:
Following the lead of some prominent Ruby developers I stopped working on controller specs for awhile, in favor of covering this functionality in my request specs (integration tests). At the time I liked this idea a lot–using tests that more closely mirrored how controller actions are accessed made good sense–but since then I’ve come back to testing controllers more explicitly, for a couple of primary reasons:
Scaffolds, when done correctly, are a great way to learn coding techniques. The spec files generated for controllers, at least as of RSpec 2.8, are pretty nice and provide a good template to help you build your own specs. Look at the scaffold generator in rspec-rails’ source, or generate a scaffold in your properly configured Rails application to begin getting a sense of these tests. (Another generator to look at is the one in Nifty Generator’s scaffolds).
A controller spec is broken down by controller method—each example is based off of a single action and, optionally, any params passed to it. Here’s a simple example:
If you’ve been following along with this series, you may notice similarities to earlier specs we’ve written:
attributes_for
option, which generates a hash of values as opposed to a Ruby object.However, there are also a couple of new things to look at:
post
), controller method (:create
), and, optionally, parameters being passed to the method.attributes_for
call to Factory Girl—not rocket science, but worth mentioning again because I had a habit early on of forgetting to use it versus default factories.Let’s start with a top-down approach. As I mentioned in the previous post on model specs, it’s helpful to think about a spec as an outline. Continuing our use of an address book app as an example, here are some things I might need to test:
And so on. As in our model specs, we can use RSpec’s describe
and context
blocks to organize examples into a clean hierarchy, based on a controller’s actions and the context we’re testing—in this case, the happy path (a user passed valid attributes to the controller) and the unhappy path (a user passed invalid or incomplete attributes). If your application includes an authentication or authorization layer, you can include these as additional contexts—say, testing with and without a logged-in user, or testing based on a user’s assigned role within the app.
With some organization in place, let’s go over some things you might want to test in your application and how those tests would actually work.
Just as in model specs, controller specs need data. Here again we’ll use factories to get started—once you’ve got the hang of it you can swap these out with more efficient means of creating test data, but for our purposes (and this small app) factories will work great.
We’ve already got a factory to generate a valid contact:
Now let’s add one to return an invalid contact:
Notice the subtle difference: The :invalid_contact
factory uses the :contact
factory as a parent. It replaces the specified attributes (in this case, firstname
with its own; everything else will defer to the original :contact
factory.
To date, the little address book app we’ve built is pretty basic. It doesn’t even require a user to log in to view it or make changes. I’ll revisit this in a future post; for now I want to focus on general controller testing practices.
A standard Rails controller is going to have four GET-based methods: #index
, #show
, #new
, and #edit
. Looking at the outline started above we can add the following tests:
Pretty simple stuff for the methods that, typically, have the lightest load to carry in controllers. In this address book app, the new
method is a little more complicated, though—it’s building some new phones to be nested in the contact information form:
How to test iterating through those phone types? Maybe something like this:
The point here is if your controller methods are doing things besides what a generator might yield, be sure to test those additional steps, too.
Let’s move on to our controller’s :create
method. Referring back to our outline, we’ve got two contexts to test: When a user passes in attributes for a valid contact, and when an invalid contact is entered. The resulting examples look something like this:
Let’s talk about that expect {}
Proc for a minute. RSpec’s readability shines here—except this code to (or to not) do something. This one little example succinctly tests that an object is created and stored. (If Proc objects seem magical to you, refer to this post by Alan Skorkin and this one by Robert Sosinski to learn more.) Become familiar with this technique, as it’ll be very useful in testing a variety of methods in controllers, models, and eventually at the integration level.
On to our controller’s update
method, where we need to check on a couple of things—first, that the attributes passed into the method get assigned to the model we want to update; and second, that the redirect works as we want. Then we need to test that those things don’t happen if invalid attributes are passed through the params:
The examples I want to point out here are the two that verify whether or not an object’s attributes are actually changed by the update
method. Note that we have to call reload
on @contact
to check that our updates are actually persisted.
Testing destroy
is relatively straightforward:
By now you should be able to correctly guess what everything’s doing. The first expectation checks to see if the destroy
method in the controller actually deleted the object; the second expectation confirms that the user is redirected back to the index upon success.
Now that I’ve shared some of the many things you can test in your controllers, let me be honest with you—it wasn’t until recently that I began testing at this level of my apps with such thoroughness. In fact, for a long time my controller specs just tested a few basics. But as you can see from RSpec’s generated examples, there are several things you can—and should—test at the controller level.
And with thoroughly tested controllers, you’re well on your way to thorough test coverage in your application as a whole. By now (between this post and the one on testing Rails models) you should be getting a handle on good practices and techniques for the practical use of RSpec, Factory Girl, and other helpers to make your tests and code more reliable.
In the MVC triumvarate, we’ve now covered the Model and Controller layers. Next time we’ll integrate the two—along with view—with RSpec request specs. Thanks as always for reading, and let me know what you think in the comments.
Ruby on Rails news and tips, and other ideas and surprises from Aaron at Everyday Rails. Delivered to your inbox on no particular set schedule.