Rails 4.0 Whirlwind Tour
Back-End Full-Stack WebRails 4.0 has yet to be released, but since it might be here by Christmas, it’s time to start getting ready.
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?
Rails 4.0 has yet to be released, but since it might be here by Christmas, it’s time to start getting ready.