Search

RSpec Tip: let and nested describe/context blocks

Andy Lindeman

2 min read

Jun 20, 2011

RSpec Tip: let and nested describe/context blocks

At Highgroove, we test our code extensively. The best testing code has solid coverage, yet also shares many of the qualities of well-written code in general, such as readability and lack of repetition.

The popular testing framework RSpec ships with constructs that aid in writing elegant testing code. One advanced technique to keep things DRY is shown after the break.

A recent story we finished for a client specified that the author of a virtual bulletin board post could later edit that post. Implicitly, users should not be able to edit posts they do not own.

One way to write tests that exercise the PUT action for the controller that handles updating posts might look like:

describe "PUT #update" do
  it "is successful when the post's author makes the request" do
    user = FactoryGirl.create(:user)
    post = FactoryGirl.create(:post, author: user)
    sign_in(user)
    put post_path(post.id), { title: "Updated Post", body: "Typos fixed!" }
    expect(response).to be_success
  end
  it "is forbidden when a non-author makes the request" do
    user = FactoryGirl.create(:user)
    post = FactoryGirl.create(:post)
    sign_in(user)
    put post_path(post.id), { title: "Updated Post", body: "Typos fixed!" }
    expect(response).to have_http_status(403)
  end
end

And for this simple example, these tests are actually pretty solid.

However, there is some obvious repetition: both tests create a post, sign in a user (though a different user each time), and make a PUT request.

The repetitive code is a good candidate for a shared before block which runs before each test, except that the tests need to sign in a different user and the user needs to be signed in before the request is made.

With a small amount of refactoring, I came up with this:

describe "PUT #update" do
  let(:user) { FactoryGirl.create(:user) }
  before do
    sign_in(user)
    put post_path(post.id), { title: "Updated Post", body: "Typos fixed!" }
  end
  context "post author is the current user" do
    let(:post) { FactoryGirl.create(:post, author: user) }
    it "is successful" do
      expect(response).to be_success
    end
  end
  context "current user is not the post author" do
    let(:post) { FactoryGirl.create(:post) }
    it "is forbidden" do
      expect(response).to have_http_status(403)
    end
  end
end

The refactorings include:

  • Nested context blocks for grouping shared context and also providing indented output when using the documentation formatter.

  • let method for defining a memoized helper method that is lazily evaluated. Notice specifically that each context defines the user helper differently, and this works correctly even though the helper is required in the outer before block.

We are always on the look out for ways to make our tests more readable and elegant. What techniques do you use in either RSpec or another framework?

Angie Terrell

Reviewer Big Nerd Ranch

Angie joined BNR in 2014 as a senior UX/UI designer. Just over a year later she became director of design and instruction leading a team of user experience and interface designers. All told, Angie has over 15 years of experience designing a wide array of user experiences

Speak with a Nerd

Schedule a call today! Our team of Nerds are ready to help

Let's Talk

Related Posts

We are ready to discuss your needs.

Not applicable? Click here to schedule a call.

Stay in Touch WITH Big Nerd Ranch News