(My) RSpec best practices and tips

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.

Post to Twitter

. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

49 Comments

  1. Posted March 28, 2010 at 19:16 | Permalink

    I had no idea half of these things existed. Cheers!

  2. Posted March 28, 2010 at 19:56 | Permalink

    Great post. I didn’t know about the specify blocks.

  3. dicouw
    Posted March 28, 2010 at 23:26 | Permalink

    Great post, very useful !

  4. Posted March 29, 2010 at 01:13 | Permalink

    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.

  5. Posted March 29, 2010 at 01:20 | Permalink

    Very nice post indeed.

    Thanks.

  6. Posted March 29, 2010 at 03:00 | Permalink

    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.

  7. Posted March 29, 2010 at 03:32 | Permalink

    Great tips!

    But there is an error in a row:
    User.new(:gender => 'W').present.should be_an IMac

    It’s should be:
    gender => 'M'

    :)

  8. burmajam
    Posted March 29, 2010 at 03:40 | Permalink

    Really great and useful examples. Wish to see them more.

    Thank you

  9. Posted March 29, 2010 at 03:50 | Permalink

    Thanks for taking the time to write this up, these are great!

  10. Posted March 29, 2010 at 07:35 | Permalink

    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.

    • Posted March 29, 2010 at 09:57 | Permalink

      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.

  11. Posted March 29, 2010 at 08:04 | Permalink

    I didn’t know about the subject/specify. Thanks Philou :)

  12. Orion Engleton
    Posted March 29, 2010 at 15:45 | Permalink

    Thanks for posting this.. This is definitely a helpful list of tips..

  13. Posted March 31, 2010 at 02:18 | Permalink

    Guys checkout Lark presentation at scotruby 20110

    http://pure-rspec-scotruby.heroku.com/

  14. Posted April 3, 2010 at 18:06 | Permalink

    Very nice.

    Thanks for reduce and clarify ans specs.

  15. Posted April 3, 2010 at 19:52 | Permalink

    Good post. Lurking on source code, I found those interesting facts:
    - it and specify are alias of example
    - context is alias of describe
    - by default, before and after have :each as default argument.

  16. howard
    Posted April 7, 2010 at 18:25 | Permalink

    “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

  17. Posted April 12, 2010 at 12:18 | Permalink

    “User when name not empty”, shouldn’t it be it { should be_valid}, with an underscore ?

  18. Posted April 13, 2010 at 06:24 | Permalink

    Great advices, thanks. I think only one handy method was missed here let(name, &block)

  19. doug livesey
    Posted April 17, 2010 at 01:38 | Permalink

    Love it, cheers — although I’m in the mood for refactoring some specs, now, & it’s weekend, dammit! :)

  20. AndyL
    Posted April 20, 2010 at 11:11 | Permalink

    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
    end
    

    I love this style – is there a trick to make it run? Or is it just for illustrative purposes?

    • Posted May 19, 2010 at 17:31 | Permalink

      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
      end
      
  21. Carmen
    Posted April 28, 2010 at 23:28 | Permalink

    Hi… This is great! Thanks..
    Can I use its with Describe and Context?

  22. Dreamcat4
    Posted July 11, 2010 at 08:11 | Permalink

    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.

  23. meervibra
    Posted September 3, 2010 at 13:44 | Permalink

    Sorry admin – my post is test

  24. Posted September 9, 2010 at 04:40 | Permalink

    Great post I would like to see an app demo showing your best practices in action.
    :D

  25. Posted January 26, 2011 at 12:40 | Permalink

    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

    • Posted January 31, 2011 at 18:20 | Permalink

      I actually use let() quite a lot. And I even made a little helper called set(). :)

      This article could end with “Prefer let() over before“.

  26. Posted March 8, 2011 at 08:52 | Permalink

    Excellent write-up on RSpec, the format is useful for going back over our own test code for ways to refactor and improve.

  27. Dan Kubb
    Posted April 1, 2011 at 23:57 | Permalink

    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.

    • Posted June 14, 2011 at 08:36 | Permalink

      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!

  28. Matt
    Posted June 25, 2011 at 14:04 | Permalink

    is there a reason why you test both:
    it { should be_valid }
    specify { @user.save.should == true }

  29. JDK
    Posted July 11, 2011 at 03:25 | Permalink

    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.

  30. Posted July 20, 2011 at 11:05 | Permalink

    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

  31. Marco Perrando
    Posted August 22, 2011 at 01:44 | Permalink

    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

  32. Posted October 19, 2011 at 02:16 | Permalink

    Hello. This is a tweet. Using twitter API for ios

  33. Posted May 8, 2012 at 07:23 | Permalink

    awesome.. didnt know 1/2 of these tricks..

  34. Ryan Cheung
    Posted June 12, 2012 at 17:37 | Permalink

    It has been 2 years since this post published. And it’s still very helpful for us. Thank you!

  35. Posted July 6, 2012 at 00:46 | Permalink

    Thank you for post!
    In code:


    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

    you missed end keyword.

    • Phil
      Posted March 16, 2013 at 07:05 | Permalink

      +1

      Thanks for tips sharing!

  36. Jack Repenning
    Posted August 9, 2012 at 18:03 | Permalink

    All great stuff, made my life 10x better in 2 minutes!

    I still have one thorny problem, though. Have any thoughts?

    My spec is testing code that creates a large object, 110 fields, with a lot of complex interrelationships. With your help, I got to this form:

    describe “the object” do
    before(:all) do
    @obj = expensively_create_the_object
    end
    it “should exist” { @obj.should_not == nil }
    it “should have proper field1″ { @obj.field1.should == 37 }
    it “should have proper field2″ { @obj.field2.should == 19 }

    end

    I tried

    specify { @obj.field2.should == 37 }
    specify { @obj.field2.should == 19 }

    which is more readable, but the test output says I pass or fail stuff like:

    should == nil
    should == nil
    should == 3
    should == “Demo”
    should not == nil
    should == nil

    Is there any way to get specify to name the tested field?

    • Posted August 19, 2012 at 02:56 | Permalink

      Thanks Jack!

      You can use a combination of subject and its for the output to make more sense.

      subject { @obj }
      
      its(:field1) { should == 37 }
      its(:filed2) { should == 19 }
      

      Also, if your object is an ActiveRecord model, you could use set which is available as a gem.

      Cheers

  37. Posted March 12, 2013 at 18:43 | Permalink

    hello
    that’s a good post.Thank you for sharing.

7 Trackbacks

  1. By 13時のヘッドライン | CROSS SBM on March 28, 2010 at 20:01

    [...] Lenovo Ideapad S10-3tレビュー : ギズモード・ジャパンはてブ:18 (My) RSpec best practices and tips | EggsOnBreadはてブ:9 [...]

  2. By Notional Slurry » links for 2010-03-31 on March 31, 2010 at 22:02

    [...] (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) [...]

  3. By rspec Short Forms « Trevor Oke on September 3, 2010 at 09:38

    [...] been trolling around a number of blogs looking at anything vaguely rspec related, when I found this post an making your rspec tests less [...]

  4. [...] (My) RSpec best practices and tips, I’m happy to share my Cucumber best practices and [...]

  5. [...] « 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 [...]

  6. By Cucumber Tips | VersaPay on March 17, 2011 at 09:25

    [...] 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. [...]

  7. By links for 2011-06-02 « Bloggitation on June 2, 2011 at 22:02

    [...] (My) RSpec best practices and tips (tags: ruby tdd rspec programming) [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>