RSpec best practices

Posted on by in Process, Web

Rspec is a great tool in the behavior driven design process of writing human readable specifications that direct and validate the development of your application. We’ve found the following practices helpful in writing elegant and maintainable specifications.

First #describe what you are doing …

Begin by using a #describe for each of the methods you plan on defining, passing the method’s name as the argument. For class method specs prefix a “.” to the name, and for instance level specs prefix a “#”. This follows standard Ruby documenting practices and will read well when output by the spec runner.

describe User do

  describe '.authenticate' do
  end

  describe '.admins' do
  end

  describe '#admin?' do
  end

  describe '#name' do
  end

end

…Then establish the #context

Write a #context for each execution path through a method; literally specifying the behavior of a method in a given context.

For example, the following method has 2 execution paths:

class SessionsController < ApplicationController

  def create
    user = User.authenticate :email => params[:email],
                             :password => params[:password]
    if user.present?
      session[:user_id] = user.id
      redirect_to root_path
    else
      flash.now[:notice] = 'Invalid email and/or password'
      render :new
    end
  end

end

Create two contexts in the corresponding spec:

describe '#create' do
  context 'given valid credentials' do
  end

  context 'given invalid credentials' do
  end
end

Note the use of “given” in the argument to each #context. It communicates the context of receiving input. Another great word to use in a context for describing conditional driven behavior is “when”.

describe '#destroy' do
   context 'when logged in' do
   end

   context 'when not logged in' do
   end
 end

By following this style, you can then nest #contexts to clearly define further execution paths.

#it only expects one thing

By striving to only having one expectation per example, you increase the readability of your specs.

A spec with multiple un-related expectations in a single example:

describe UsersController do

  describe '#create' do
    ...

    it 'creates a new user' do
      User.count.should == @count + 1
      flash[:notice].should be
      response.should redirect_to(user_path(assigns(:user)))
    end
  end

end

Breaking out the expectations into separate examples clearly defines the behavior and results in easier to maintain examples:

describe UsersController do

  describe '#create' do
    ...

    it 'creates a new user' do
      User.count.should == @count + 1
    end

    it 'sets a flash message' do
      flash[:notice].should be
    end

    it "redirects to the new user's profile" do
      response.should redirect_to(user_path(assigns(:user)))
    end
  end

end

Write examples by starting with a present tense verb that describes the behavior.


    it 'creates a new user' do
    end

    it 'sets a flash message' do
    end

    it 'redirects to the home page' do
    end

    it 'finds published posts' do
    end

    it 'enqueues a job' do
    end

    it 'raises an error' do
    end

Finally, don’t begin examples names with the word ‘should’.  It’s redundant and results in hard to read spec output. Likewise, don’t hesitate to use words like ‘the’ or ‘a’ or ‘an’ in your examples when they improve readability.

Prefer explicitness

#it, #its and #specify may cut down on the amount of typing but they sacrifice readability.  You now have to read the body of the example in order to determine what its specifying.  Use these sparingly if at all.

Let’s compare the documentation formatter output of the following:

describe PostsController do

  describe '#new' do
    context 'when not logged in' do
      ...

      subject do
        response
      end

      it do
        should redirect_to(sign_in_path)
      end

      its :body do
        should match(/sign in/i)
      end
    end
  end

end

With the explicit behavior descriptions below:

describe PostsController do

  describe '#new' do
    context 'when not logged in' do
      ...

      it 'redirects to the sign in page' do
        response.should redirect_to(sign_in_path)
      end

      it 'displays a message to sign in' do
        response.body.should match(/sign in/i)
      end
    end
  end

end

The first example results in blunt, code-like output with redundancy from using the word ‘should’ multiple times:

$ rspec spec/controllers/posts_controller_spec.rb --format documentation

PostsController
  #new
    when not logged in
      should redirect to "/sign_in"
      should match /sign in/i

The second results in a very clearly, readable specification:

$ rspec spec/controllers/posts_controller_spec.rb --format documentation

PostsController
  #new
    when not logged in
      redirects to the sign in page
      displays a message to sign in

Run specs to confirm readability

Always run your specs with the ‘–format’ option set to ‘documentation’ (in RSpec 1.x the –format options are ‘nested’ and ‘specdoc’)

$ rspec spec/controllers/users_controller_spec.rb --format documentation

UsersController
  #create
    creates a new user
    sets a flash message
    redirects to the new user's profile
  #show
    finds the given user
    displays its profile
  #show.json
    returns the given user as JSON
  #destroy
    deletes the given user
    sets a flash message
    redirects to the home page

Continue to rename your examples until this output reads like clear conversation.

Use the right matcher

RSpec comes with a lot of useful matchers to help your specs read more like language.  When you feel there is a cleaner way … there usually is!

Here are some of our favorites matchers, before and after they are applied:

# before: double negative
object.should_not be_nil
# after: without the double negative
object.should be

# before: 'lambda' is too low level
lambda { model.save! }.should raise_error(ActiveRecord::RecordNotFound)
# after: for a more natural expectation replace 'lambda' and 'should' with 'expect' and 'to'
expect { model.save! }.to raise_error(ActiveRecord::RecordNotFound)

# before: straight comparison
collection.size.should == 4
# after: a higher level size expectation
collection.should have(4).items

Check out the docs and ask around.

Formatting

Use ‘do..end’ style multiline blocks for all blocks, even for one-line examples. Further improve readability with a single blank line between all blocks and at the beginning and end of the top level #describe.

Again compare:

describe PostsController do
  describe '#new' do
    context 'when not logged in' do
      ...
      subject { response }
      it { should redirect_to(sign_in_path) }
      its(:body) { should match(/sign in/i) }
    end
  end
end

With the clearly structured code below:

describe PostsController do

  describe '#new' do
    context 'when not logged in' do
      ...

      it 'redirects to the sign in page' do
        response.should redirect_to(sign_in_path)
      end

      it 'displays a message to sign in' do
        response.body.should match(/sign in/i)
      end
    end
  end

end

A consistent formatting style is hard to achieve with a team of developers but the time saved from having to learn to visually parse each teammate’s style makes it worthwhile.

Conclusion

As you can see, all these practices revolve around writing clear specifications readable by all developers. The ideal is to run all specs to not only pass but to have their output completely define your application. Every little step towards that goal helps, and we’re still learning better ways to get there. What are some of your best RSpec practices?

Update: This post is now available in Japanese thanks to Makoto Kuwata: http://jp.rubyist.net/magazine/?0032-TranslationArticle


Feedback

  Comments: 56


  1. Great post!! I hope you don’t mind but i posted it to rubyflow.com


  2. Nice one. How refreshing to see clean code and practicality.


  3. Nice post. Good practices are always welcome.


  4. If I’m not supposed to include “should”, then why do all the automatically generated descriptions include it?

    As in the output from:

    it do
    should redirect_to(sign_in_path)
    end

    I’m not necessarily disagreeing with you, but RSpec is. :)


    • We were talking about the argument given to #it message, not #should itself. Clearly you should be using #should ;-)

      Of course, when the spec is run with documentation you would see a “should” appear in the output. We do feel that describing the example with a verb makes for less redundancy.


  5. What about shared contexts? Let’s say that my class has an attribute, which state modifies expected results of bunch of other methods. Should I put a separate block of describes for those methods, or should I add a ‘when the x is set to y’ to each global describe ‘#method’ block? The later one seems a little bit more redundant in my opinion.


  6. You could not have written this at a better time, I’ve just committed to converting an old Rails 2.3 site to Rails 3 and am making sure the site has full set of stories and tests. There is so much around for RSpec but difficult to get a simple concise picture of where to start. Thanks.

  7. Prodis a.k.a. Fernando Hamasaki de Amorim


    Great post. I will introduce the use of present tense verb rather than should in my team.


  8. Wow, high marks for thinking this through as much as you have and for the cleverness of the section titles. Tweeted and bookmarked.


  9. “By striving to only having one expectation per example, you increase the readability of your specs.”

    This is generally good advice, but I’ve definitely seen it taken too far, and I’m sure I’ve been guilty of that. I also think that the benefit of one expectation per example isn’t primarily readability; it’s separating out the specifications of different behaviors into different examples so that the failure output tells your clearly which behavior is failing. Increased readability is a nice side effect, though.

    So, to rephrase what you said a bit: you should strive to specify one behavior per example, and if you have multiple expectations in the same example, it’s a good sign that you may be specifying multiple behaviors and you should consider separating them into separate examples.

    There are times when it’s absolutely correct to have multiple expectations in the same example. Here’s an example from RSpec’s own specs:

    http://github.com/rspec/rspec-core/blob/v2.0.1/spec/rspec/core/let_spec.rb#L19-22


  10. @ross – I duplicate the #context for each #describe. For example, say I have 2 actions in a controller that require being logged in, #new and #create. I’ll have 1 describe for each of those methods and the same 2 contexts (when logged in, when not logged in) nested within each #describe. I prefer to have 1 place where all the specs for a method are located. If I had 2 #contexts (when logged in, when not logged in) and then nested the #describe blocks in each I’d have 2 #describe’s per method. I think this approach is fine as well, either way you have duplication.

    @myron – Excellent point, I agree with you on saying “one behavior per example”. I’ve definitely had examples with more than 1 expectation.

    @all – Thanks for all the feedback!


  11. I love that kind of post that suggest a nice way to work. I was looking for something like this about RSpec. Thanks for sharing!

  12. Michael MacDonald


    A great article! I particularly like how you aren’t telling everyone else that they are doing it wrong, just showing us what has worked for you. I didn’t know about using “expect” instead of “lambda” so I’ll definitely give that a try.


  13. If you’re doing a lot of response asserting, you should use these matchers for response codes: https://github.com/c42/rspec_matchers

    Gives you stuff like
    response.should be_unprocessable_entity
    response.should be_im_a_teapot


  14. Nice article!

    I mean will be nice to have a similar article about best practices on Steak for acceptance specs, I know it can be almost the same, but for acceptance specs is common to check things on the way of execution (ending with a lot of assertions on same spec).

    Thanks a lot!


  15. great post how clear cut code saving and testing

  16. Prem Sichanugrist


    Thank you for this nice article. However, I’m curious about testing the validation. Do you just making another `describe ‘validations’` block, or do you just don’t test it altogether?


  17. @prem – I spec model validations using “describe ‘#valid?'” and if necessary nested contexts such as “on create” or “on an update”.


  18. Nice article brow!

    Don’t you think that…

    its(:body) { should match(/sign in/i) }

    … is readable enough?


  19. @ nicolasiensen – thanks for the feedback

    I just prefer visually parsing Strings as opposed to punctuation like parens, symbols, and curly braces. In the end its just a personal preference that I feel results in the most easily readable specs.


  20. This post is great. So I will translate it into Japanese.
    I’ll let you the url after translation is prepared.
    Thank you for nice post.


  21. This is one of the best posts I’ve read so far on the subject. Thanks!


    • This is one of the best posts I’ve read on testing, ever. Actually, it’s one of the best posts I’ve read on programming practice ever.


  22. Hi,
    Thank you for this excellent post, I have a question though.

    You don’t explicitly refer to the HTTP verb in your examples, how would you add it ? Or do you think this is enough to have it implicit (ok for usual methods, but if I want to add some others I will need to choose a verb..)

    • Jared Carroll


      arnodmental,

      I used to include the HTTP verb in my example names. Since I tend to write RESTful controllers if felt redundant to mention the HTTP verb because I was just following Rails’ conventions e.g. #new is a GET, #create is a POST, etc. As for non-RESTful actions (i.e. NOT one of the normal 6 that a map.resources will give you) I’d probably still exclude the HTTP verb so its consistent with the other specs for RESTful actions.


  23. Great!
    How about multiline expect blocks?
    Do you use { } or do..end?


    • When it comes to multiline examples I follow the Ruby multiline block convention of using do...end. I never use 1 line blocks with { } because I prefer consistent block usage throughout a file.

  24. Иван Зотов


    Great! Thank you!


  25. About #it only expects one thing…
    Whats the impact on performance of integration tests? I mean, I’m repeating the same request over and over again.
    It would be interesting to run a benchmark on that. Don’t you think?


    • Good point pcasaretto. You will definitely slow your tests down by writing an example for each expected behavior. In my opinion, the tradeoff of speed for clearer test failures is worth it. However, I have refactored large, multiple example contexts down into a single example when it’s obviously slowing down the tests. I tend to not pre-optimize my tests and only worry about performance when it becomes an issue.


    • You should check rspec-steps (https://github.com/LRDesign/rspec-steps) for integration testing – faster, still readable (or even sc readable) and just for the purpose


  26. Nice article. What are your thoughts on using #let?


    • I avoid #let and #let!. Every time I’ve tried to adopt #let into my specs I end up getting burned because #let is lazily initialized. Then I have to use #let!. I’d rather just setup all my instance variables in a #before which I know will always be executed before exercising the system under test. So I just use #before across the board and not a mix and match of #let and #let!.


      • I think the lazy initialization is part of their usefulness. It also helps with readability:

        let(:order) { Factory(:order, custom_options} }

        is better than putting it in a before block.

      • Gonzalo Arreche


        If it’s a precondition for your test, you wanna put it inside a before. But if it’s a collaborator, you could put it in a let, so you initialize it only when/if you need it.


  27. Thanks for the pointers. Quick question: How did you get “response.should redirect_to(user_path(assigns(:user)))” to work? When I try that, I get an error.

  28. Matias H. Leidemer


    Hey, great post! It’s old but it remais new :-)

    Just thinking about the “User.count.should == @count + 1″. I mean, should I check the database to see if the user got created? I’d rather use something like “User.should_receive(:create)”. IMHO it will make my tests run faster and isolated from the db layer.

    What do you think?


    • Mocking the database will make your model specs faster. However, if you’re mocking the database, remember to have an integration test that actually does test the real objects.

      I typically don’t mock the database in my model specs. I’ve always found traditional state-based testing more straightforward and elegant than interaction-based testing with mocks. However, using state-based testing and an outside-in testing style will result in a test suite consisting of many “mini-integration tests”. But, by making no assumptions about implementation, your code will be easier to refactor without breaking tests.

      Either approach is fine. It often comes down to the team and how comfortable they are with both styles.

Your feedback