Alexander
Alexander

Reputation: 4237

RSpec Cannot Use Defined Variables in Shared Examples

I'm having trouble using a defined variable for a shared example in RSpec. Here's my test:

RSpec.shared_examples "check user logged in" do |method, action, params|
  it "redirects to the sign in page if the user is not logged in" do
    send(method, action, params)
    expect(response).to redirect_to(signin_url)
  end
end

RSpec.describe UsersController, type: :controller do
  describe "GET #show" do
    let(:user) { FactoryGirl.create(:user) } 
    let!(:show_params) do 
      return { id: user.id }
    end

    context "navigation" do
      include_examples "check user logged in", :get, :show, show_params
    end
  end
end

In the test, I'm checking to make sure the user needs to be logged in before the action can be performed. I am receiving the following error message:

method_missing': show_params is not available on an example group

What do I need to change to make show_params accessible? I've tried using it_behaves_like instead of include_examples with no luck. I've also tried removing the context "navigation" block to no avail. I need to perform this check across multiple controllers and actions, so it seems a shared example might be the correct way to reuse code.

Upvotes: 2

Views: 1944

Answers (1)

max
max

Reputation: 101811

The problem here is that the memoized let helper show_params is called outside of an example.

Instead of passing the params you can simply reference a let from the outer scope where you are including the example:

RSpec.describe UsersController, type: :controller do
  let(:user) { FactoryGirl.create(:user) }
  describe "GET #show" do
    let(:action) { get :show, id: user }
    it_should_behave_like "an authorized action"
  end
end

RSpec.shared_examples "an authorized action" do
  it "denies access" do
    action
    expect(response).to redirect_to(signin_url) 
  end
end

This is a pretty powerful pattern that lets you use a convention over configuration approach since the last let always wins.

RSpec.describe UsersController, type: :controller do
  let(:user) { FactoryGirl.create(:user) }
  describe "GET #show" do
    let(:action) { get :show, id: user }
    it_should_behave_like "an authorized action"

    context "when signed in" do
      before { sign_in user }
      let(:action) { get :show, id: other_user }
      context 'when viewing another user' do
        it_should_behave_like "an authorized action"
      end
    end
  end
end

Upvotes: 3

Related Questions