Deprecating controller specs?

568 views
Skip to first unread message

Stefan Kanev

unread,
May 14, 2016, 1:04:51 PM5/14/16
to rs...@googlegroups.com

Hi everybody.

I recently watched a talk that said controller specs are getting deprecated in the next version of RSpec. I found that surprising. I’m definitely not trying to push against this decision, but I would really like to understand its logic.

I’ve thought long and hard and I was not able to convince myself that controller specs are unhelpful. Of course, in order to be useful, they require a certain style of writing specs and controllers. I’d like to explain my approach and I’d really love to get some feedback. Am I missing something that invalidates my logic?

Let’s start with “my” style of controllers. They should contain as little logic as possible and delegate everything else to collaborators (a model or a service). Each controller essentially follows the same pattern:

  1. It picks a bunch of stuff from params
  2. It passes them to a model/service that carries out the work
  3. It decides what to do next based on the outcome (render a template or redirect somewhere)

The create action in the default scaffold are a great example. To summarise, a controller:

  • delegates (most of) the work to a model/service;
  • is responsible for figuring out what to pass to the model/service;
  • is responsible for deciding where to send the user next;
  • usually communicates with a single model/service over a thin (1-2 methods) interface;
  • uses a small number (1-2) of instance variables to pass to the view.

Now, following this style, the spec is written like so:

  • Collaborators are replaced with doubles
  • Just to be clear, the database isn’t hit, neither in setup nor verification
  • Views are not rendered
  • Expectations are set on (1) messages sent to collaborators, (2) the HTTP response (redirect, render, etc) and (3) variables passed to the view.

As far as I can tell, this is the GOOS style of testing, applied to controllers – collaborators are mocked and the interaction itself is tested, not the end result. If memory serves right, that’s also how The RSpec Book talks about controller specs. If you want an example, you can check this controller and this spec I wrote a while ago.

I’m under the impression that this is the popular style of controller specs in the RSpec community, although I might be wrong. I’m reiterating it only to make sure we’re on the same page.

So, anyway: assuming controller specs are written that way, I think they are indeed useful. Just not in the same way as feature or model specs.

The point of view I subscribe to, is that automated testing is not just about catching regressions (Safety Net). It’s about many things, like documentation, driving design, productivity, to name a few. Yes, the Safety Net of controller specs is nowhere near what you get out of feature or request specs. But that’s not why I write controller specs. I write them because they help design. Namely, the spec:

  • gives feedback that helps keep the interface between controller and collaborator simple;
  • puts positive pressure on the design direction – another developer is less likely to extend the controller with the business logic and more likely to put in the service;
  • helps move the logic away from the controller to a model/service, where it can be tested in isolation and relatively faster (compared to request/feature).

I admit that when I was starting, this was a tricky concept to get right. But once I grokked it, it was pivotal to my understanding of how to keep the controller simple. Controller specs have helped me learn how to do better design and they keep helping me to keep my designs clean.

It goes without saying, but I also write feature specs that also cover (some of) the logic in integration.

So, conclusion time. If you’ve gone this far into reading this, I thank you for your time, and I would really like to hear what you think.

To loop back to the beginning, controller specs are getting deprecated. Justin suggests using request specs instead. I neither feel that I will benefit from stopping, nor I see how replacing them with request specs is better. Hence, I don’t understand the decision to deprecate controller specs.

What am I missing?

Xavier Shay

unread,
May 14, 2016, 1:09:54 PM5/14/16
to rs...@googlegroups.com
which talk?
--
You received this message because you are subscribed to the Google Groups "rspec" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rspec+un...@googlegroups.com.
To post to this group, send email to rs...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Myron Marston

unread,
May 14, 2016, 1:16:55 PM5/14/16
to rs...@googlegroups.com

Great questions, Stefan!

Let me preface my answer by saying: I’m not a rails user (and haven’t been for ~5 years) so my understanding of the situation may not be quite correct. Sam Phippen, Jon Rowe, or Aaron Kromer should be able to give you a more definitive answer but I’ll weigh in anyway.

The TL;DR is that the RSpec team has not chosen to deprecate controller specs — the Rails team has. Controller specs in rspec-rails wrap the rails controller functional testing infrastructure. The Rails team has chosen to remove this from Rails 5 — which means rspec-rails simply can’t support controller specs with Rails 5 out-of-the-box.

Luckily, the rails team has moved the controller testing bits into the rails controller testing gem, and writing controller specs with Rails 5 (and RSpec 3.5) should work if you add that gem to your Gemfile.

As for the whether or not controller specs are a good idea: as with all things, it depends. Historically, Rails has had 2 similar (but slightly different) kinds of tests, and rspec-rails followed suit:

  • Controller specs
  • Request specs

They look vaguely similar and the guidelines of when to use one vs the other is not very clear. Both allow you to simulate requests and make assertions about responses. The main difference is that controller specs instantiate your controller class and call methods on it directly (bypassing the routing layer and rack middleware stack) whereas request specs go through routing and rack middleware. This has a couple notable effects:

  • Controller specs were noticeably faster since they did so much less
  • Controller specs are full of gotchas, where something that a user would expect to work would not work because to work properly the request needs to go through one or more rack middlewares. In general, Rails controllers are designed to interact with rack middleware for all sorts of things so when you eliminate the middleware, the results can be strange and unintuitive. We’ve had many bug reports on rspec-rails that basically boiled down to “that’s just how controller specs work, so use a request spec instead for what you are trying to test.”

From what I understand, the Rails team has worked on making the full request specs faster in Rails 5 so that they are basically at speed parity with old controller specs. This means that the main benefit of controller specs over request specs (speed) no longer exists. Thus, the recommendation of the rails core team is to not write controller tests anymore, and write request tests instead.

In general, that’s sound advice. For a specific project, controller specs might still make sense if you are really looking for the kind of isolation controller specs give you and understand the gotchas.

I haven’t done Rails in a long time, but if I was — I’d generally try to keep my controllers free of much logic (delegating requests to bare ruby classes that can easily be tested in isolation) which means I’d tend to favor request specs as a way to ensure things work end-to-end and bypass controller specs altogether. On your project that may not be sound advice, though.

HTH,
Myron

--


Myron Marston

unread,
May 14, 2016, 1:17:47 PM5/14/16
to rs...@googlegroups.com
Xavier: I believe Stefan is talking about this talk from Justin Searls from rails conf:


Allen Madsen

unread,
May 14, 2016, 5:56:41 PM5/14/16
to rs...@googlegroups.com
Stefan, I use request specs in the same way that you describe
controller specs. The primary reason I do that is because of quirks
I've experienced with controller specs. For example, if you define a
route as post :create, in your test, you can still call it as get
:create, because it doesn't go through the router.

I second everything Myron said.
Allen Madsen
http://www.allenmadsen.com
> https://groups.google.com/d/msgid/rspec/CADUxQmuHdh2-FkGoudPCOD%2BMPAC9uraV6DDUz3wZgZjmh9fLkw%40mail.gmail.com.

Sam Phippen

unread,
May 15, 2016, 6:46:50 AM5/15/16
to rs...@googlegroups.com
Most of the answers here are very good, but here’s my perspective in case it helps:

1) controller specs/tests themselves are not deprecated in either Rails 5 or RSpec.
2) #assigns and #assert_template *are* deprecated by Rails 5 (https://github.com/rails/rails/issues/18950)
3) The rails controller testing gem restores #assigns and #assert_template
4) If the rails controller testing gem is present, RSpec will automatically integrate with it
5) Controller tests are in my opinion generally a smell, as Justin covered well in his talk

RSpec respects semver, and so when we release rspec-rails 3.5.0, your controller tests *will* continue to function.
As a note, semver allows the maintainers to decide the compatibility strategy. The compatibility strategy for Rails 5
is that out of the box, your controller tests won’t work if they use assigns and assert_template without the controller
testing gem. The reason for this is that we didn’t want to force-install the controller testing gem on people who don’t
use controller tests. Adding the gem will give you everything you need.

Thanks
Sam Phippen

Stefan Kanev

unread,
May 20, 2016, 2:39:45 PM5/20/16
to rspec
The answers have been very helpful.

Thank you.
Reply all
Reply to author
Forward
0 new messages