(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. Both comments and trackbacks are currently closed.
  • http://randomrules.org Eoin

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

  • http://matthewcarriere.com Matthew Carriere

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

  • Pingback: 13時のヘッドライン | CROSS SBM

  • dicouw

    Great post, very useful !

  • http://wwww.21croissants.com Jean-Michel

    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.

  • http://gnomeslab.com dbalmeida

    Very nice post indeed.

    Thanks.

  • http://prodis.pro.br Prodis a.k.a. Fernando Hamasaki de Amorim

    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.

  • http://blog.lucascaton.com.br/ Lucas Catón

    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'

    :)

    • http://pcreux.com Philippe Creux

      Thanks Lucas! Just fixed! :)

  • burmajam

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

    Thank you

  • http://lukerandall.github.com Luke Randall

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

  • http://mwilden.blogspot.com Mark Wilden

    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.

    • http://pcreux.com Philippe Creux

      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.

  • http://www.dmathieu.com Damien

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

  • Orion Engleton

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

  • http://wwww.21croissants.com Jean-Michel

    Guys checkout Lark presentation at scotruby 20110

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

    • http://pcreux.com Philippe Creux

      I was about to post this link here. :)

  • Pingback: Notional Slurry » links for 2010-03-31

  • http://raphael.dealmeida.wordpress.com Raphael de Almeida

    Very nice.

    Thanks for reduce and clarify ans specs.

  • http://php.apsique.com Clbustos

    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.

  • howard

    “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

    • http://pcreux.com Philippe Creux

      :)

  • http://www.serabe.com Serabe

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

    • http://pcreux.com Philippe Creux

      Fixed. Thanks!

  • http://isyruby.wordpress.com Pitr

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

  • doug livesey

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

  • AndyL

    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?

    • http://eggsonbread.com Philippe Creux

      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
      
  • Carmen

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

    • http://eggsonbread.com Philippe Creux

      Sure! It is not implemented in RSpec 2 though.

  • Dreamcat4

    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.

  • Pingback: rspec Short Forms « Trevor Oke

  • meervibra

    Sorry admin – my post is test

  • Pingback: (My) Cucumber best practices and tips | EggsOnBread

  • http://boliviaonrails.com Boris Barroso

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

  • Pingback: Show validation errors in RSpec be_valid output using a custom matcher

  • http://ruby-lambda.blogspot.com/ Ho-Sheng Hsiao

    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

    • http://eggsonbread.com Philippe Creux

      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“.

  • http://fiveamsoftware.com Walter Yu

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

  • Pingback: Cucumber Tips | VersaPay

  • Dan Kubb

    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.

    • http://eggsonbread.com Philippe Creux

      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!

  • Pingback: links for 2011-06-02 « Bloggitation

  • Matt

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

  • JDK

    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.

  • http://gusiev.com/ Bogdan Gusiev

    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

  • Marco Perrando

    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

  • http://googleLazn.com RAYMUNDO

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

  • http://www.simplysitters.com Jason Woodlee

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

  • Ryan Cheung

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

  • http://railsguides.net Andrey

    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

      +1

      Thanks for tips sharing!

  • Jack Repenning

    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?

    • http://eggsonbread.com Philippe Creux

      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