Sundered Peak

through the mind of kyle tolle

Using Before Blocks In RSpec

RSpec is a handy tool for writing tests in Ruby. Writing tests means constantly learning. It takes a long time to learn what to test, how to test, and how not to bite yourself in the ass later. And even then, you’ll learn more as you write your next set of tests.

The latest tests I’ve worked with are controller specs, to assert the behavior and output from Rails controller actions.

They have evolved to look something like:

Note: The code blocks below have some pseudocode. I want to convey my meaning here, rather than give 100% working code.

require 'spec_helper'

describe SomethingsController do
  describe 'GET index' do
    describe 'with an existing something' do
      let!(:something) { FactoryGirl.create :something }
      let(:action)     { get :index }

      # This is what we're interested in, for this post.
      before do
        action
      end

      it 'returns okay' do
        expect(actual_status).to eq expected_status
      end

      it 'has the right output' do
        expect(actual_output).to eq expected_output
      end
    end
  end
end

The topic of this post is the before block. It calls the action before each it block runs.

This helps reduce duplication across assertions, particularly when there are many. It also ensures we run the action for each assertion. It can be easy to overlook a missing action call.

But, there is a downside to this pattern. I believe RSpec runs before blocks as they are encountered. This means that, if you include shared examples, which may include other contexts and other shared examples, then your tests might not run as you expect.

Consider if we now want to authorize an API token given with the request. Since we want to do this across many controllers, we can include contexts and shared examples to ensure this same behavior in many areas.

require 'spec_helper'

describe SomethingsController do
  describe 'GET index' do
    describe 'with an existing something'
      # NEW!
      include_context 'with API token'

      let!(:something) { FactoryGirl.create :something }
      let(:action)     { get :index }

      before do
        action
      end

      # NEW!
      include_examples 'authorize API token'

      it 'returns okay' do
        expect{actual_status}.to eq expected_status
      end

      it 'has the right output' do
        expect{actual_output}.to eq expected_output
      end
    end
  end
end

The shared example would look something like:

shared_examples 'authorize API token' do
  describe 'with no API token' do
    before do
      remove_header :api_token
    end

    it 'returns unauthorized status' do
      expect(actual_status).to eq unauthorized_status_code
    end
  end

  describe 'with invalid API token' do
    before do
      set_header :api_token, 'AnInvalidApiTokenHere'
    end

    it 'returns unauthorized status' do
      expect(actual_status).to eq unauthorized_status_code
    end
  end
end

Now, when we run the specs, we get a failure. The actual_status is 200 instead of the 401 we expect. Why is this?

In the shared examples, we modify the request’s headers, right? But we didn’t modify the action like we expected to. The action already ran, thanks to that before block in the controller spec. So the action used the regular, valid headers, because the contexts in which we modify the headers were included after the action had run.

We didn’t modify the action/request before it ran, like we wanted. So we didn’t get the status we expected.

But we can fix this issue. The solution is to yank the before { action } piece, and call the action in each it block. This way we can modify contexts as we need, and only run the action right before we check the assertion. This is really what we want.

The controller specs, updated as described above, would look something like:

require 'spec_helper'

describe SomethingsController do
  describe 'GET index' do
    describe 'with an existing something'
      include_context 'with API token'

      let!(:something) { FactoryGirl.create :something }
      let(:action)     { get :index }

      # We removed the before block with the action...

      include_examples 'authorize API token'

      it 'returns okay' do
        #NEW!
        action
        expect{actual_status}.to eq expected_status
      end

      it 'has the right output' do
        #NEW!
        action
        expect{actual_output}.to eq expected_output
      end
    end
  end
end

And here are the shared examples:

shared_examples 'authorize API token' do
  describe 'with no API token' do
    before do
      remove_header :api_token
    end

    it 'returns unauthorized status' do
      # NEW!
      action
      expect(actual_status).to eq unauthorized_status_code
    end
  end

  describe 'with invalid API token' do
    before do
      set_header :api_token, 'AnInvalidApiTokenHere'
    end

    it 'returns unauthorized status' do
      # NEW!
      action
      expect(actual_status).to eq unauthorized_status_code
    end
  end
end

We must remember to manually call the action for each assertion, but we gain the benefit of greater flexibility. This is important for the tests we write now, and also those we’ll write in the future.

Oh yeah, since we kept things simple and reduced astonishment, our specs run like we expect them to. Now our tests are back to green!