Patrick Ward words, code, and music

Understanding Subject in RSpec 2

I needed to write some shared examples in RSpec the other day for a simple ActiveRecord concern I was writing.

In doing so, it forced me to understand what RSpec means by the “subject” of an example. I’ll admit this is a very simple concept, but until now I didn’t take the time to understand it and the relish docs weren’t overly descriptive on how it works.

In general, the subject of an example is just the object being described. But what, specifically, does that mean?

In an implicit context, it means the subject is an instance of the object being described. For example, in a model spec, I might be describing a User class:

    describe User do
      it { should respond_to :authenticate }
    end

Which can also be written as:

    describe User do
      subject.should respond_to :authenticate
    end

Here, the subject is an instance of User. RSpec creates an instance of User by calling new on the User class without any arguments. It’s the same as saying this:

    describe User do
      subject { User.new }
      it { should respond_to :authenticate }
    end

That last example, is what RSpec calls an explicit subject. Instead of deducing the subject from the outer describe, an explicit subject is declared within the example and can be used afterwards the same way that an implicit subject would.

The key distinction for me, and one that I didn’t understand previously, was that the subject is an instance of the class being described (implicitly or explicitly).

This came up when I needed to test both an instance of a subject as well as a static class method of a subject’s parent class.

But how do I test a static class method against an instance?

Simple, just call class on the instance.

    describe User do
      subject.class.some_class_method.should do_something
    end

It’s all very simple, but I just hadn’t grokked it until now. Fortunately, this realization came in handy when I was writing a simple shared example to test common functionality against multiple models.

In this case I wanted to test that a model had a specific instance method as well as some class-based scopes.

    shared_examples_for "a Trashable" do |model_type|

      before(:each) do
        @objects = [
          Factory(model_type, :trashed => false),
          Factory(model_type, :trashed => true)
        ]
      end

      it "should respond to a trash method" do
        subject.should respond_to :trash
      end

      # Note - have to call class on the implicit subject to call a class method!
      it "should not show trashed subjects by default" do
        subject.class.all.should_not include(@objects.second)
      end

      it "should show trashed subjects" do
        subject.class.trashed.should include(@objects.second)
        subject.class.trashed.should_not include(@objects.first)
      end
    end

By using subject and passing in the model_type to generate some factories, I’m able to dynamically adjust to whatever model the shared example exists within at the time. Simple, dry, and efficient.