Skip to content

Testing protected controllers

Aaron Brager edited this page Mar 21, 2020 · 14 revisions

Few things you should be aware of when testing controllers protected by doorkeeper.

Request Specs

If you're using RSpec, the recommendation from their release notes is to use request specs, not controller specs:

The official recommendation of the Rails team and the RSpec core team is to write request specs instead.

Here's one example of an RSpec request test:

describe 'Credentials', type: :request do
  context 'when unauthorized' do
    it 'fails with HTTP 401' do
      get '/me'
      expect(response).to have_http_status(:unauthorized)
    end
  end

  context 'when authorized' do
    let(:application) { FactoryBot.create(:application) }
    let(:user)        { FactoryBot.create(:user) }
    let(:token)       { FactoryBot.create(:access_token, application: application, resource_owner_id: user.id) }

    it 'succeeds' do
      get '/me', params: {}, headers: { 'Authorization': 'Bearer ' + token.token }
      expect(response).to be_successful
      expect(response.body).to eq(user.to_json)
    end
  end
end

Scopes

If you have an action that requires a specific scope, you can specify the scopes in your factory, for example:

FactoryBot.define do
  factory :access_token, class: 'Doorkeeper::AccessToken' do
    application
    expires_in { 2.hours }
    scopes { 'public' }
  end
end

Controller Specs

Valid tokens

in the majority of cases, you'll only need to stub the doorkeeper_token method in your controller:

describe Api::V1::ProfilesController do
  describe 'GET #index' do
    let(:token) { instance_double('Doorkeeper::AccessToken', :acceptable? => true) }

    before do
      allow(controller).to receive(:doorkeeper_token) { token }
      # controller.stub(:doorkeeper_token) { token } # => RSpec 2
    end

    it 'responds with 200' do
      get :index, :format => :json
      response.status.should eq(200)
    end
  end
end

Stubbing

  • :acceptable? => true will bypass the doorkeeper filter, since the token is valid
  • :acceptable? => false will force the response status to be 401 unauthorized

You can also stub the resource_owner_id to have the stubben token authenticate a specific user, e.g.:

let(:token) { instance_double('Doorkeeper::AccessToken', acceptable?: true, resource_owner_id: user.id) }

Scopes

If you have an action that requires a specific scope, you will need to stub the token scope:

# controllers/api/v1/profiles_controller.rb
class Api::V1::ProfilesController < ApiController
  before_action :only => [:create] do
    doorkeeper_authorize! :write
  end
  # ...
  
  def create
    respond_with 'api_v1', Profile.create!(params[:profile])
  end
end

# spec/controllers/api/v1/profiles_controller_spec.rb
describe 'POST #create (with scopes)' do
  let(:token) do
    stub :acceptable? => true, :scopes => [:write]
  end

  before do
    allow(controller).to receive(:doorkeeper_token) { token }
    # controller.stub(:doorkeeper_token) { token } # => RSpec 2
  end

  it 'creates the profile' do
    Profile.should_receive(:create!) { stub_model(Profile) }
    post :create, :format => :json
    response.status.should eq(201)
  end
end

Integration

If you need to test the controller fully integrated with your app, you'll need to create the necessary models:

describe Api::V1::CredentialsController do
  describe 'GET #me (integrated)' do
    let!(:application) { Factory :application } # OAuth application
    let!(:user)        { Factory :user }
    let!(:token)       { Factory :access_token, :application => application, :resource_owner_id => user.id }

    it 'responds with 200' do
      get :me, :format => :json, :access_token => token.token
      response.status.should eq(200)
    end

    it 'returns the user as json' do
      get :me, :format => :json, :access_token => token.token
      response.body.should == user.to_json
    end
  end
end

MiniTest::Spec

You can accomplish the same test above with MiniTest (no Rspec) by creating a mock Token and stubbing the acceptable? method:

describe API::V1::ProfilesController do
  describe 'unauthorized' do
    it "should return unauthorized" do
      get :index
      assert_response :unauthorized
    end
  end

  describe 'authorized' do
    let(:user) { users(:one) }
    let(:token) { Minitest::Mock.new }
    setup do
      token.expect(:acceptable?, true, [Doorkeeper::OAuth::Scopes])
    end

    it "should return success" do
      @controller.stub :doorkeeper_token, token do
        get :index
        assert_response :success
      end
    end
  end
end

More examples

For more examples, check the doorkeeper provider app on GitHub here.

Clone this wiki locally