1

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?

Aaron Brager
  • 65,323
  • 19
  • 161
  • 287

2 Answers2

1

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

Yury Matusevich
  • 988
  • 12
  • 22
  • Thanks, the links you gave were really helpful. Looks like `sign_in` won't work in my case though since I am using doorkeeper with access tokens. – Aaron Brager Mar 19 '20 at 20:06
1

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!

Anthony
  • 15,435
  • 4
  • 39
  • 69
  • this got me a lot closer, thanks! I'm still having trouble generating a valid access token though. I'll post a separate question about that. – Aaron Brager Mar 19 '20 at 20:07
  • Here's my followup question if you're interested: https://stackoverflow.com/q/60764500/1445366 – Aaron Brager Mar 19 '20 at 20:24