Using Shared Examples in RSpec

Shared examples is a great RSpec feature that allows you to reuse test cases in different context. One use case is when you need to test different access level to a resource depending on the role. Lets say you have a resource that can have published/draft state. There are several roles with different access level to this resource.

  • Editor - has access to published/draft resources
  • Viewer - has access to published resources only

One way you could implement RSpec test for this is by writing separate tests for each role:

# Assume we have set up the data for @editor, @viewer, @published_resource and @draft_resource
context 'as editor' do
  before(:each) { make_request_as(@editor) }
  it 'can view published resources' do
    get "/resources/#{@published_resource.id}"
    # expect to get the resource
  end
  it 'can view draft resources' do
    get "/resources/#{@draft_resource.id}"
    # expect to get the resource
  end
end

context 'as viewer' do
  before(:each) { make_request_as(@viewer) }
  it 'can view published resources' do
    get "/resources/#{@published_resource.id}"
    # expect to get the resource
  end
  it 'cannot view draft resources' do
    get "/resources/#{@draft_resource.id}"
    # expect NOT to get the resource
  end
end

There is a little duplication on testing if the user “can view published resource”. But the test is not too terrible. However, lets say you have additional requirement to add 2 more user roles.

  • Admin - has access to published/draft resources
  • Non logged in user - has access to published resources only

Now you have to duplicate the test case for editor to test admin permission, and duplicate the test case for viewer to test the non-logged in user. That is a lot of duplication. A better way is to use shared_examples.

shared_examples 'user who can view published resources' do
  it 'can view published resources' do
    get "/resources/#{@published_resource.id}"
    # expect to get the resource
  end
end

shared_examples 'user who can view draft resources' do
  it 'can view draft resources' do
    get "/resources/#{@draft_resource.id}"
    # expect to get the resource
  end
end

shared_examples 'user who CANNOT view draft resources' do
  it 'CANNOT view draft resources' do
    get "/resources/#{@draft_resource.id}"
    # expect NOT to get the resource
  end
end

shared_examples is basically a grouping of examples that can be reused in different contexts by including it using the it_behaves_like '...' method. So now we can include the right shared examples in each role context.

context 'as editor' do
  before(:each) { make_request_as(@editor) }
  it_behaves_like 'user who can view published resources'
  it_behaves_like 'user who can view draft resources'
end

context 'as admin' do
  before(:each) { make_request_as(@admin) }
  it_behaves_like 'user who can view published resources'
  it_behaves_like 'user who can view draft resources'
end

context 'as viewer' do
  before(:each) { make_request_as(@viewer) }
  it_behaves_like 'user who can view published resources'
  it_behaves_like 'user who CANNOT view draft resources'
end

context 'as non-logged in user' do
  it_behaves_like 'user who can view published resources'
  it_behaves_like 'user who CANNOT view draft resources'
end

Not only it’s less duplication, it’s so nice to read. That’s the beauty of RSpec :)