After a year using RSpec, I’m happy to share “(My) RSpec Best Practices and Tips”. Let’s make your specs easier to maintain, less verbose, more structured and covering more cases!
Use shortcuts specify {}, it {} and subject {}
You think RSpec is verbose? In case your code doesn’t need any description, use a specify block!
it "should be valid" do @user.should be_valid end
can be replaced with
specify { @user.should be_valid }
RSpec will generate a nice description text for you when running this expectation. Even better, you can use the it block!
describe User do
it { should validate_presence_of :name }
it { should have_one :address }
end
In case the subject is the not the class described, just set it with the subject method:
subject { @user.address }
it { should be_valid }
Start context with ‘when’/'with’ and methods description with ‘#’
Have you ever get a failed test with an incomprehensible error message like:
User non confirmed confirm email wrong token should not be valid
Start your contexts with when and get nice messages like:
User when non confirmed when #confirm_email with wrong token should not be valid
Use RSpec matchers to get meaningful messages
In case of failure
specify { user.valid?.should == true }
displays:
'User should == true' FAILED expected: true, got: false (using ==)
While
specify { user.should be_valid }
displays:
'User should be valid' FAILED expected valid? to return true, got false
Nice eh?
Only one expectation per it block
I often see specs where it blocks contain several expectations. This makes your tests harder to read and maintain.
So instead of that…
describe DemoMan do
it "should have expected attributes" do
demo_man = DemoMan.new
demo_man.should respond_to :name
demo_man.should respond_to :gender
demo_man.should respond_to :age
end
end
… do this:
describe DemoMan do
before(:all) do
@demo_man = DemoMan.new
end
subject { @demo_man }
it { should respond_to :name }
it { should respond_to :gender }
it { should respond_to :age }
end
(Over)use describe and context
Big specs can be a joy to play with as long as they are ordered and DRY. Use nested describe and context blocks as much as you can, each level adding its own specificity in the before block.
To check your specs are well organized, run them in ‘nested’ mode (spec spec/my_spec.rb -cf nested).
Using before(:each) in each context and describe blocks will help you set up the environment without repeating yourself. It also enables you to use it {} blocks.
Bad:
describe User do
it "should save when name is not empty" do
User.new(:name => 'Alex').save.should == true
end
it "should not save when name is empty" do
User.new.save.should == false
end
it "should not be valid when name is empty" do
User.new.should_not be_valid
end
it "should be valid when name is not empty" do
User.new(:name => 'Alex').should be_valid
end
it "should give the user a flower when gender is W" do
User.new(:gender => 'W').present.should be_a Flower
end
it "should give the user a iMac when gender is M" do
User.new(:gender => 'M').present.should be_an IMac
end
end
Good:
describe User do
before { @user = User.new }
subject { @user }
context "when name empty" do
it { should not be_valid }
specify { @user.save.should == false }
end
context "when name not empty" do
before { @user.name = 'Sam' }
it { should be_valid }
specify { @user.save.should == true }
end
describe :present do
subject { @user.present }
context "when user is a W" do
before { @user.gender = 'W' }
it { should be_a Flower }
end
context "when user is a M" do
before { @user.gender = 'M' }
it { should be_an IMac }
end
end
end
Test Valid, Edge and Invalid cases
This is called Boundary value analysis, it’s simple and it will help you to cover the most important cases. Just split-up method’s input or object’s attributes into valid and invalid partitions and test both of them and there boundaries. A method specification might look like that:
describe "#month_in_english(month_id)" do
context "when valid" do
it "should return 'January' for 1" # lower boundary
it "should return 'March' for 3"
it "should return 'December' for 12" # upper boundary
context "when invalid" do
it "should return nil for 0"
it "should return nil for 13"
end
end
I hope this will help you improve your specs. Let me know if I missed anything! :)
You could also be interested in (My) Cucumber best practices and tips or rspec-set a little gem that helps you speeding up your model specs.
Entries (RSS)
Follow me on Twitter
Github repos
LinkedIn profile
Photos
Videos
43 Comments
I had no idea half of these things existed. Cheers!
Great post. I didn’t know about the specify blocks.
Great post, very useful !
Thanks Phillip for a such a good looking post with interesting content :-)
http://twitter.com/L4rk from hashrocket made a presentation about RSpec and mentionned most of your tricks ;-)
I have been working with RSpec since 2007 and although I like lean and beautifull code with syntaxix sweets such as subject and be_* matchers, I am not sure this is the way to go to make the “BDD” door easy to open.
Have a look to http://adamblog.heroku.com/past/2008/2/7/minimalist_rspec_matching/
On the other hand, I wonder how I will do BDD in French or another language. Did you have a though about it? I think I will probably end translating the RSpec API into French to avoid the Frenglish uglyness.
Very nice post indeed.
Thanks.
I agree with you and this practices are mine too.
Since I started reading “The RSpec Book”, I improved the way to write my tests.
But, I didn’t know the shortcuts yet.
Thank you for the post.
Great tips!
But there is an error in a row:
User.new(:gender => 'W').present.should be_an IMacIt’s should be:
gender => 'M':)
Thanks Lucas! Just fixed! :)
Really great and useful examples. Wish to see them more.
Thank you
Thanks for taking the time to write this up, these are great!
I have never agreed with the “one expectation per test” dictum. In the example you show, the first version is more readable and easier to maintain that the second one, in my opinion. Even though multiple failures can occur in the first version, it is trivial to track down which expectation broke. And the first version does a better job of actually specifying the code under test, since it explicitly shows what attributes are required for a valid object.
The first version is also faster, of course, but that shouldn’t be a primary consideration.
In more complicated scenarios, testing one situation with one point of failure makes sense. But not in all scenarios, and not in this one.
Good article, though.
Thanks for your comment Mark!
I agree that the first version is faster and it also does the job very well because the example is simple. As you said it’s worth using this on complex examples. I had to deal with specs where it blocks contained ~15 lines to set up the context (most of them were copy/pasted over it blocks) then 30 lines mixing expectations and context updates.
I’ll make sure to use more relevant examples in my next articles.
Cheers.
I didn’t know about the subject/specify. Thanks Philou :)
Thanks for posting this.. This is definitely a helpful list of tips..
Guys checkout Lark presentation at scotruby 20110
http://pure-rspec-scotruby.heroku.com/
I was about to post this link here. :)
Very nice.
Thanks for reduce and clarify ans specs.
Good post. Lurking on source code, I found those interesting facts:
-
itandspecifyare alias ofexample-
contextis alias ofdescribe- by default,
beforeandafterhave:eachas default argument.“I had to deal with specs where it blocks contained ~15 lines to set up the context (most of them were copy/pasted over it blocks) then 30 lines mixing expectations and context updates.”
@pcreux i hang my head in shame
:)
“User when name not empty”, shouldn’t it be it { should be_valid}, with an underscore ?
Fixed. Thanks!
Great advices, thanks. I think only one handy method was missed here let(name, &block)
its(attribute, &block) was missed too. :)
Love it, cheers — although I’m in the mood for refactoring some specs, now, & it’s weekend, dammit! :)
Your last section describing how to do method specifications is interesting.
describe "#month_in_english(month_id)" do context "when valid" do it "should return 'January' for 1" # lower boundary it "should return 'March' for 3" it "should return 'December' for 12" # upper boundary context "when invalid" do it "should return nil for 0" it "should return nil for 13" end endI love this style – is there a trick to make it run? Or is it just for illustrative purposes?
It’s just for illustrative purpose. You might actually do:
["January" => 1, "March" => 3, "December" => 12].each do |v, r| it "should return '#{r}' for '#{v}'" do month_in_english(v).should == r end endHi… This is great! Thanks..
Can I use its with Describe and Context?
Sure! It is not implemented in RSpec 2 though.
There are some useful insights, but I would also say that theres no harm in having multiple expectations in a single it block.
I find that listing @object1.expectation1, @object2.expectation2 in the order they are executed is easier to maintain. When you edit a method and go back, its much faster to insert-in-place where the new expectations should go.
Where a method branches (if/else) is where to draw a new test, and use method branching to determine where to nest the object state in context blocks. So first write a single it spec for the default calling path, then a single it spec test for the alternate calling path, and then write the (less likely) error cases, exceptions, corner cases etc. Its meant to be an effecient way to write tests and maximize the regression coverage. The methods I write have a mostly linear execution, so the approach works well.
Wheras your recommendations seem better for those methods than have a greater non-deterministic characteristic, and no clear execution path.
Sorry admin – my post is test
Great post I would like to see an app demo showing your best practices in action.
:D
Hi,
I am trying to get the word about the most significant yet least-used feature of Rspec 1.3: let()
You should not have to use instance variables or before() blocks at all. (Before blocks become something you’d use to memoize things). Along with ActiveSupport::Concern, it lets you extend the Rspec 2+ DSL.
I’ve redone one of your examples to use it. … how about updating your Best Rspec Practices post?
Thanks,
Ho-Sheng Hsiao
I actually use let() quite a lot. And I even made a little helper called set(). :)
This article could end with “Prefer
let()overbefore“.Excellent write-up on RSpec, the format is useful for going back over our own test code for ways to refactor and improve.
One other tip is the described_class method that allows you to reference the class/module argument passed into your describe block. Its nice in normal specs and even more useful in shared specs.
Thanks for that! I usually setup a subject before running shared specs (but it’s a weird dependency that needs to be documented). I can’t wait to try described_class!
is there a reason why you test both:
it { should be_valid }
specify { @user.save.should == true }
Very interesting things–learned a lot from this, so thanks! I’m left with a question though–what do you see wrong with verbosity? I’ve never had a problem with verbosity. My feeling is that if the code is verbose, without impacting performance, it is easier for other who have to deal with it to understand it.
That is a good start with all set of rspec features. And in addition you may combine context, subject and let and receive completely new opportunities.
Good example of what you can do: http://gusiev.com/dorspec/#24
I’ve discovered that you can use “subject” to modify your object under test in the before blocks, avoiding the reduntant blocks in wich you define a @user and set it as the subject.
So you can shorten even more your test to
describe User do
context "when name empty" do
it { should not be_valid }
specify { subject.save.should == false }
end
context "when name not empty" do
before { subject.name = 'Sam' }
it { should be_valid }
specify { subject.save.should == true }
end
describe :present do
subject { subject.present }
context "when user is a W" do
before { subject.gender = 'W' }
it { should be_a Flower }
end
context "when user is a M" do
before { subject.gender = 'M' }
it { should be_an IMac }
end
end
end
Hello. This is a tweet. Using twitter API for ios
awesome.. didnt know 1/2 of these tricks..
7 Trackbacks
[...] Lenovo Ideapad S10-3tレビュー : ギズモード・ジャパンはてブ:18 (My) RSpec best practices and tips | EggsOnBreadはてブ:9 [...]
[...] (My) RSpec best practices and tips | EggsOnBread "After a year using RSpec, I’m happy to share “(My) RSpec Best Practices and Tips”. Let’s make your specs easier to maintain, less verbose, more structured and covering more cases!" (tags: rspec Ruby programming BDD behavior-driven-design best-practices tips testing) [...]
[...] been trolling around a number of blogs looking at anything vaguely rspec related, when I found this post an making your rspec tests less [...]
[...] (My) RSpec best practices and tips, I’m happy to share my Cucumber best practices and [...]
[...] « nuby on rails, which has more details but still didn’t work. (Also the way I also found (My) RSpec best practices and tips | EggsOnBread, which is too good not to share.) With some more searching I found a good walk-through on writing [...]
[...] After (My) RSpec best practices and tips, I’m happy to share my Cucumber best practices and tips! This article will help you organize, clarify and reduce the size of your cucumber scenarios. [...]
[...] (My) RSpec best practices and tips (tags: ruby tdd rspec programming) [...]