Making RSpec concise
A common criticism of RSpec is that it is very verbose. I don’t necessarily agree (or care), but I thought it would be fun to see how concise I could make my specs.
Here are some simple specs from a client project:
describe Company do
before do
@company = Company.new
end
it "should have many classifications" do
@company.should have_many(:classifications)
end
it "should have many industries through companies" do
@company.should have_many(:industries, :through => :classifications)
end
it "should have many locations" do
@company.should have_many(:locations)
end
it "should have many leads" do
@company.should have_many(:leads)
end
it "should have many jobs" do
@company.should have_many(:jobs)
end
it "should have many notes" do
@company.should have_many(:notes)
end
it "should have many phones ordered by phone type position" do
@company.should have_many(:phones, :as => :phonable,
:include => :phone_type, :order => 'phone_types.position')
end
it "should have many events" do
@company.should have_many(:events)
end
it "should have many interests" do
@company.should have_many(:interests)
end
it "should belong_to an exchange" do
@company.should belong_to(:exchange)
end
it 'should belong_to a primary_contact' do
@company.should belong_to(:primary_contact, :class_name => 'Job')
end
it 'should have many articles' do
@company.should have_many(:articles,
:order => 'articles.date DESC, articles.created_at DESC')
end
end
These specs check the declared associations on our company model using some custom matchers. They are not very complicated, but are somewhat repetitive. Each example has a description that is basically a duplication of the implementation.
Step 1: remove the description
For a while now, RSpec has had the ability for matchers to be self describing. If you don’t pass a block to #it, it uses the description provided by the matcher.
it do
@company.should have_many(:jobs)
end
When that spec is run, it gives the output “should have a has_many association called :jobs”. Depending on what you’re speccing, the built in description isn’t always clear, but in this case it’s great.
See #simple_matcher if you want to create custom matchers with useful error messages.
Step 2: remove the subject
So the duplication within each example is gone, but if you look at the full spec above, each example calls @company.should. Accessing an instance variable isn’t what I would consider “duplication”, but thanks to a nifty new feature added to RSpec today, it’s now unnecessary noise. We can simply call #should within our example, and it will use a new instance of the described type as the “subject”.
describe Company do
it do
should have_many(:jobs)
end
end
You can customize the subject if you don’t simply want a new instance.
describe Company, 'validations' do
subject { Company.new(valid_company_attributes) }
it do
should be_valid
end
end
Note: As David Chelimsky points out in the comments, this is not released yet and is subject to change.
Step 3: One-liner
Lastly, we can use the one line block:
describe Company do
it { should have_many(:classifications) }
it { should have_many(:events) }
it { should have_many(:interests) }
it { should have_many(:jobs) }
it { should have_many(:leads) }
it { should have_many(:locations) }
it { should have_many(:notes) }
it { should have_many(:articles, :order => 'articles.date DESC, articles.created_at DESC') }
it { should have_many(:industries, :through => :classifications) }
it { should have_many(:phones, :as => :phonable, :include => :phone_type, :order => 'phone_types.position') }
it { should belong_to(:exchange) }
it { should belong_to(:primary_contact, :class_name => 'Job') }
end
That’s pretty sexy. I wasn’t able to do this with all of the specs in my app, but it worked with quite a few of them.
9 comments
Brandon, this is fantastic, I can definately see usage for the subject block in my specs.
I was wondering though, do you know if the subject block is executed once before each “it” block, similar to a before() block, or does it execute once for the whole describe block and cache the results? I’m hoping the later because often in my specs I want to run the method only one time and then use the “it” blocks to test the return values, and object state changes.
It’s especially important when there’s DB access involved because you’re sort of penalized for fine grained specs.
I just tried edge Rspec and it appears that subject block is executed once for each it block.
It also appears as if the subject block doesn’t have access to the ivars you define in your before block. That means you can’t setup an object in a known state, use that object in the subject block and check the object state in the it blocks. It makes it hard to write stuff like: “it { should equal(@object) }” or “it { should_not change(@object, :count) }” for example.
This refactoring looks very nice.
Off topic, what I really like is the theme for your syntax highlighting! :)
Looks like shoulda :)
Just an FYI – this is NOT yet released, and not yet complete. The syntax is subject to change (pun intended) as I’m not 100% sold this is the right word yet. The feature will be released, but the word “subject” might become something else. Or it might not. Just keep that in mind as you choose to use this unreleased feature :)
The motivation for adding this feature is that none of these options really work for me:
it do reader.should_not have_to_ask("do it?") end it { reader.should_not have_to_ask("why am I an explicit it?") specify { reader.should_not have_to_ask("what's the difference between specify and it?")Being able to assign an implicit receiver to #should solves all three of those problems.
@bryanl – the subject bit was a collaborative effort w/ Joe Ferris from thoughtbot, so, yes, looks like shoulda :)
@Dan Kubb – #subject is only called when the example block does not have an explicit receiver for #should:
subject { RubyConfAttendee.new } it { should enjoy_hanging_with_fellow_rubyists } it "should spend more time hacking and less time drinking" do attendee = RubyConfAttendee.new(:average_beers_per_night => 7) attendee.should spend_more_time_hacking_and_less_time_drinking endIn this case the first example calls the subject block and delegates #should to the result. The second example does not call the subject block.
As for accessing what’s in before(:each), I’m not clear on what you’re trying to do, but please share your ideas with the rspec google group and/or submit feature requests to lighthouse.
Brandon,
This is def. cool; I dig that you don’t need to specify the instance var (potentially) anymore. I used to use one-liners for stuff like this:
it { @topic.should validate_presence_of(:title) } it { @topic.should belong_to(:forum) } it { @topic.should belong_to(:creator) }
but, obviously, taking out @topic would read a lot easier. Nice find; let’s hope this gets rolled into RSpec!
David, can you tell us more about lambda?
David,
Thanks for you comments. I’ve added a note to the post.
I finally went ahead a did my rendition of something similar but inspired by story runner. It has the potential to make pending examples disappear magically.
- Git: http://git.oss.semanticgap.com/spec_heat.git
- Eyes: http://git.oss.semanticgap.com/gitweb/gitweb.cgi?p=spec_heat.git;a=tree
It just takes a handful of:Speak your mind: