Aaron Brager
Aaron Brager

Reputation: 66302

RSpec: Stub controller method in request spec

I'm writing an RSpec request spec, which looks roughly like (somewhat shortened for brevity):

describe 'Items', type: :request do
  describe 'GET /items' do
    before do
      allow_any_instance_of(ItemsController).to receive(:current_user).and_return(user)
      get '/items'
      @parsed_body = JSON.parse(response.body)
    end

    it 'includes all of the items' do
      expect(@parsed_body).to include(item_1)
      expect(@parsed_body).to include(item_2)
    end
  end
end

The controller looks like:

class ItemsController < ApplicationController
  before_action :doorkeeper_authorize!
  def index
    render(json: current_user.items)
  end
end

As you can see, I'm trying to stub doorkeeper's current_user method.

The tests currently pass and the controller works as expected. My question is about the line:

allow_any_instance_of(ItemsController).to receive(:current_user).and_return(user)

I wrote this line based on the answers in How to stub ApplicationController method in request spec, and it works. However, the RSpec docs call it a "code smell" and rubocop-rspec complains, "RSpec/AnyInstance: Avoid stubbing using allow_any_instance_of".

One alternative would be to get a reference to the controller and use instance_double(), but I'm not sure how to get a reference to the controller from a request spec.

How should I write this test avoid code smells / legacy testing approaches?

Upvotes: 1

Views: 3510

Answers (2)

Anthony
Anthony

Reputation: 15987

You're supposed to be on vacation.

I think the right way is to avoid stubbing as much as you can in a request spec, doorkeeper needs a token to authorize so I'd do something like:

describe 'Items', type: :request do
  describe 'GET /items' do
    let(:application) { FactoryBot.create :oauth_application }
    let(:user)        { FactoryBot.create :user }
    let(:token)       { FactoryBot.create :access_token, application: application, resource_owner_id: user.id }
    before do
      get '/items', access_token: token.token
      @parsed_body = JSON.parse(response.body)
    end

    it 'includes all of the items' do
      expect(@parsed_body).to include(item_1)
      expect(@parsed_body).to include(item_2)
    end
  end
end

Here are some examples of what those factories might look like.

Lastly, nice SO points!

Upvotes: 1

Yury Matusevich
Yury Matusevich

Reputation: 998

have you thought not to mock current_user at all?

if you write a test helper to sign in a user before your request spec, current_user will be populate automatically as if it was a real user. The code would look like this:

before do
  sign_in user
  get '/items'
  @parsed_body = JSON.parse(response.body)
end

if you are using devise gem for authentication it has a nice written wiki page about that here.

This approach is also recommended here by @dhh

Upvotes: 1

Related Questions