When I use controller/request specs and when I don’t

by Jason Swett,

Every so often I come across a question of controller or request specs and when to use them.

Before I can address this I need to get some terminology out of the way. In RSpec there are controller tests and request specs which are similar but subtly different. Let’s talk about what the difference is.

Controller specs vs. request specs

As of (apparently) July 2016, the Rails team and the RSpec core team recommend using request specs instead of controller specs. Here’s why, in their words:

For new Rails apps: we don’t recommend adding the rails-controller-testing gem to your application. The official recommendation of the Rails team and the RSpec core team is to write request specs instead. Request specs allow you to focus on a single controller action, but unlike controller tests involve the router, the middleware stack, and both rack requests and responses. This adds realism to the test that you are writing, and helps avoid many of the issues that are common in controller specs. In Rails 5, request specs are significantly faster than either request or controller specs were in rails 4, thanks to the work by Eileen Uchitelle1 of the Rails Committer Team.

If I had to extract a TL;DR from this I’d put it this way: “The RSpec team recommends that we use request specs instead of controller specs. The reason is that request specs provide a little bit more of a realistic test bed than controller specs”.

So henceforth in this post I won’t be mentioning controller specs, only request specs.

I only use request specs in three scenarios

My default policy is not to use request specs at all. I find that fine-grained model specs and coarse-grained feature specs give me a sufficient level of coverage. Plus the fact that I put very little code in my controllers means that controller-focused tests would add relatively little value.

There are three specific scenarios though where I find request specs helpful.

Scenario 1: legacy projects

If my controllers are skinny to the point of being skeletal then I don’t feel a strong need to test them directly (although I certainly still test the controller behavior indirectly with feature specs).

If on the other hand my controllers are morbidly obese, as they often are in Rails legacy projects, then I find request specs to be a very handy tool.

When I encounter a bloated controller method my first desire is just to scoop out the body of that method and move it to either a new or existing model object. Unfortunately that’s not always a safe option due to the code in that method being tightly coupled to the particulars of that controller. Adding to the risk is that I often don’t understand exactly what the controller method is supposed to do.

So in these scenarios I’ll write a characterization test in the form of a few request specs that cover that controller method’s behavior.

These request specs will bring me two benefits. First, the process of interrogating the code via tests will often help me gain a decent level of understanding of what the code is doing. Second, the tests themselves will provide a safety net that mitigates the risk of any change.

In this case my request specs hopefully have a limited lifespan. Hopefully my request specs will assist me in moving the controller code into models. Then I can reincarnate the request specs as model specs.

Scenario 2: APIs

I said earlier that I tend not to use request specs because the combination model specs and feature specs usually gives me the level of confidence I desire.

Since APIs don’t have GUIs that can be exercised, feature specs are out of the picture. So in these cases I instead use request specs.

Hopefully my API code is designed such that most of the heavy lifting is done by the models and tested by model specs. I don’t want to have to test a whole bunch of code path permutations via request specs. If there’s complexity to be tested, I want it to be tested via a model spec. To me a request spec on an API serves the same general purpose as a feature spec on a complete web application which is to answer the question: “Do all the layers of the application work together?”

When I’m coding a new “traditional” Rails app I actually disable request specs along with a handful of other spec types.

Scenario 3: it’s too awkward or costly to write a feature spec

There are some scenarios where it would be too awkward, impractical or slow to write a full feature spec for the controller behavior I want to test.

For example, let’s say I want the delete action of a controller to allow deletion of the resource under certain conditions but not under certain other conditions. It might be too slow or cumbersome to write a full feature spec for this, so in this case I might just write a request spec.

4 thoughts on “When I use controller/request specs and when I don’t

  1. Phil

    Well presented arguments and interesting food for thought.

    How do feel about Scenario 3: Security Testing
    For example, I might want to test that certain pages are not accessible to unprivileged users. In my opinion, this is not a ‘feature’. I like to keep my features reserved for testing interactions that a user might legitimately want to make, not things that they shouldn’t be able to do. In this scenario I would prefer a request spec to assert any nefarious requests are met with a 403 Forbidden or similar.

    Reply
    1. akz92

      I agree. Sometimes it’s important to ensure that only a set of users can perform certain actions. Testing the policy class helps a lot but it’s still not enough IMO.

      Reply
  2. Nathan

    After years of working on legacy web apps (several of which started with little or no test coverage) in multiple languages, I find that request specs are my go to. It is often hard to tell if unit tests are realistic. If I can’t get to model code from a request, then by definition it’s dead code, but my tests can’t tell me that if I have tests for that code. In my view request specs are actually testing the true interface for the code. I reserve unit tests for libraries that are really getting shared with clients that I don’t work with directly. Models that are used in exactly one application are more like implementation details. This approach leaves me much freer to make larger scale changes to the models while ensure that the end user experience is not effected. What is the compelling argument for model specs and features in a web app?

    Reply
    1. Jason Swett Post author

      I can see the merit in what you’re saying and at a high level I would say I pretty much agree. I like to have a good amount of end-to-end tests/feature specs that tell me ALL the layers of my application are working together because, like you say, model specs/unit tests alone don’t give you that assurance. I prefer using feature specs for this as opposed to request specs because feature specs exercise the UI while request specs only request everything from the request down, leaving out the UI. I find that model specs have a place in my applications because I feel that the overhead of feature specs isn’t a good match for testing a large number of fine-grained use cases. I might test 2-4 common cases using feature specs and then test 5-10 scenarios using model tests that are more toward the “edge case” end of the spectrum because I find model specs cheaper to write, run and maintain.

      Reply

Leave a Reply

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