RSpec best practices
Posted on
by
Jared Carroll in Development, Process
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