ruby-on-railsrspecdoorkeeper

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?


Solution

  • 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!