TL;DR

Explicit use of the “subject” abstraction is a code smell, and should be refactored to use a more intention revealing name whenever possible.

One liners

rspec-core supports a one-liner syntax to reduce the noise of common requirements like validations:

Without support for this syntax, the same example might look like this:

The benefit of this more verbose example is that it we can read it and understand all the parts right away: an object is initialized and assigned to a local variable, then that variable is used for an expectation.

The benefit of the one-liner is that it’s terse and expressive, but that comes at a cost: you can’t see what the expectation is being evaluated against, so you have to understand some underlying mechanics in order to isolate/understand a failure.

The subject abstraction

Behind the scenes, the one-liner uses a “subject” abstraction supported by two methods named subject. One is a class method on ExampleGroup, used to declare the “subject” of all of the examples in the group:

The other is an instance method on ExampleGroup. The first time it is called within an example the block passed to the class’ subject method is evaluated and its result memoized, returning the same value from that and each subsequent subject call:

Here is what they look like together:

The problem with this example is that the word “subject” is not very intention revealing. That might not appear problematic in this small example because you can see the declaration on line 3 and the reference on line 6. But when this group grows to where you have to scroll up from the reference to find the declaration, the generic nature of the word “subject” becomes a hinderance to understanding and slows you down.

In this case, we’d be better served by using a method (or a let declaration) with an intention revealing name:

If we can do that, you might wonder why we have “subject” at all. Well, it was originally designed to never be seen:

Implicit subject

Note in the example with subject { Article.new }, that the subject declaration is initializing an Article with no args. Since RSpec knows that the first argument to describe is the Article class, it can store a similar block in the background as a default, implicit subject declaration, leaving us with:

That’s a little better, but now subject appears out of nowhere in the example, leaving the reader to wonder where it came from. To remove the need for explicitly referencing subject, the example delegates should and should_not to subject when it is, itself, the receiver:

Starting that line with “should” seems a bit odd though, so the final step is to do it all in one line:

Now we have a completely implicit subject, and the result reads quite nicely in both the code and the output when run with --format documentation:

Article
  should validate presence of :title

We still need to trust that something is doing some work for us but it’s all operating at the same level of abstraction, so we don’t have to try to interpret half of the functionality.

Avoid explicit use of subject

Intention revealing names are crucial if you want to be able to quickly scan and understand code as you navigate around different parts of a system. This is as true for specs as it is for implementation, and the generic nature of the word “subject” makes it a poor choice when a more intention revealing name can be used.

Is an explicit subject ever OK?

Guidelines are guidelines; YMMV. In general I would recommend that if there is a reasonable way to use an intention revealing name instead of “subject”, you should. The only use case I can think of in RSpec in which another name can’t be used is the one liner syntax:

Here we have to use subject because that’s the only way to tell RSpec where to send should and should_not. In my opinion, any other explicit appearance of subject can and should be refactored to use an intention revealing name.

Update 2012-05-15

Based on feedback on this post, I added support for a “named subject,” which lets you reference the declared subject implicitly in one-liners and with an intention revealing name in standard examples:

This will be released with rspec-core-2.11. Keep your eyes out for it!

16 Responses to “Spec smell: explicit use of subject”

  1. Harold Giménez Says:

    And a pattern that I’ve seen over and over that makes tests even less maintainable is the use of #its with a really contrived subject. For example:

    subject { people.to_hash[0] }

    a few lines down

    its([:age]) { should == 18 } its([:gender]) { should == ‘M’ }

    etc.

    Please, let’s stop this :)

  2. David Says:

    Harold - agree entirely. We’re actually planning to extract #its to a separate gem when we release rspec-3. There are already a couple of subject/its gems out there that we may recommend if they cover all the bases #its does now. See https://github.com/dnagir/its, https://github.com/ZenCocoon/rspec-subject-extensions

  3. Avdi Grimm Says:

    This really reduces the convenience of shared specs, if we can’t rely on subject referring to the unit under test.

    I’d like to see a compromise that let us name the thing under test while also letting RSpec know that it IS the thing under test. How would you feel about something like this:

    https://gist.github.com/2705101

  4. Bradley Schaefer Says:

    Or why not just make ’subject’ be a special kind of ‘let’?

    https://gist.github.com/2705200

    Even there, I’m not sure that exposing ’subject’ is worth it. Hm.

  5. David Says:

    Avdi, Bradley - check out https://github.com/rspec/rspec-core/issues/619

    I was actually working on that at home already as I wrote this article, and decided NOT to do it, but now that you guys thought of it, I’ll go ahead and add it.

  6. Yi Wen Says:

    Nice write, although I don’t find implicit assertions are easier to understand than using subject explicitly. sorry.

  7. Gerry Says:

    While reading the article I was about to suggest pretty much the same thing and then I got to the update at the bottom of the article and instantly became very happy.

    Fantastic change. :)

  8. Matt Says:

    Why not use the

    let(:something) { Something.new }

    basically completes the same thing.

  9. Lincoln K. Says:

    Hm, that’s pretty informative! Nice to learn it!

  10. Kevin McCaughey Says:

    Your update to this has really confused me Dave. I came from the deck of cards example (http://benscheirman.com/2011/05/dry-up-your-rspec-files-with-subject-let-blocks) then saw the link to this. I am currently using Michael Hardl’s tutorial.

    So first it is good, then you say it’s bad, then you say it might be good and have updated it, but then Michael uses it in his tutorial (Subject { page } ) for testing a page.

    I am really, really confused about why it’s good or bad. I understand there are opinions, but for a relative beginner (I am on chapater 9 of the rails tutorial) all the conflicting advice leaves me perplexed.

    I also have your book (”The Rspec Book”) and the testing code in this seems different again from what I see out in the wild (i.e. above site and Rails Tutorial).

    In fact, I would say that the most difficult and time consuming part of the last 6 months of learning Rails has been about 20% to do with Rails and 80% wondering what the testing code is doing and what the syntax means, seeing as everyone seems to use it differently.

    I am not criticising you - I just think we need some kind of clarity, otherwise beginners like me are going to be wasting loads of time vacillating over how to test rather than coding.

  11. Kevin McCaughey Says:

    That all sounded a bit grumpy - I don’t mean it to, it’s more a heartfelt plea :) I do appreciate rspec greatly. It’s just it is the source of a lot of confusion because of the (seemingly) diametrically oppsed views I keep reading (and that’s after I simply stopped reading DHH’s comments - guy needs some diplomacy lessons ;)

  12. Ric Says:

    It looks like most of your code samples are missing for some reason in this post.

  13. David Says:

    Ric - I saw that and then refreshed and everything showed up. How’s that for confidence-building? I’ll look into what that’s happening.

  14. Snuggs Says:

    David. How does one do acceptance testing?

    e.g.:

    describe ‘Voting’ do let(:person) { Factory.attributes_for :person }

    before do visit ‘/addperson’ register person submit_vote_for person end

    it ‘increases vote count by one’ do find(’#vote_count’). text.should eq ‘1′ end end

  15. Snuggs Says:

    Check this gist. https://gist.github.com/snuggs/5554348

  16. Make Money online Says:

    Woah this specific site is fantastic i enjoy studying your posts. Keep up the excellent do the job! You understand, many persons are looking round due to this info, you are able to enable them to drastically.

Leave a Reply