# Testing

> Write correct and complete RSpec tests for Doorkeeper. Use when adding specs for new features, writing regression tests for bug fixes, or reviewing test coverage for a change.

- Skill: `doorkeeper-gem/testing` (Agent Skill)
- Install (CLI): `npx skillmds@latest add doorkeeper-gem/testing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/doorkeeper-gem/testing/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: doorkeeper-gem (https://skillmd.com/u/doorkeeper-gem)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/doorkeeper-gem/testing

---


# Testing

When writing or modifying tests in Doorkeeper, use this skill to ensure tests are correct, complete, and follow project conventions.

## Test Organization

| What you're testing | Where to put the spec |
|--------------------|----------------------|
| OAuth request/response flow (integration) | `spec/requests/flows/` |
| Endpoint behavior (HTTP layer) | `spec/requests/endpoints/` |
| Controller logic (unit) | `spec/controllers/` |
| Model behavior & mixins | `spec/models/doorkeeper/` |
| OAuth protocol logic (unit) | `spec/lib/oauth/` |
| Configuration | `spec/lib/config_spec.rb` |
| Generators | `spec/generators/` |
| Routing | `spec/routing/` |
| Grape integration | `spec/grape/` |

## Spec Conventions

### Style

```ruby
# frozen_string_literal: true

require "spec_helper"

RSpec.describe Doorkeeper::OAuth::SomeClass do
  describe "#method_name" do
    context "when condition is met" do
      it "does the expected thing" do
        # arrange, act, assert
      end
    end
  end
end
```

### Require Line

All specs use `require "spec_helper"`. The `spec_helper` loads the dummy Rails app, database, factories, and support helpers — it covers both unit and integration needs.

> **Note:** A legacy `spec_helper_integration` file exists but is just a compatibility wrapper around `spec_helper`. Do not use it in new specs.

### Factories

Factories are in `spec/factories.rb`. Use FactoryBot:

```ruby
let(:application) { FactoryBot.create(:application) }
let(:access_token) { FactoryBot.create(:access_token, application: application) }
let(:access_grant) { FactoryBot.create(:access_grant, application: application) }
```

### Helper Methods

Reuse helpers from `spec/support/helpers/`:

- `model_helper.rb` — `client_exists`, `create_resource_owner`, token/grant existence checks
- `request_spec_helper.rb` — `json_response`, `should_have_status`, `url_should_have_param`, `basic_auth_header_for_client`
- `url_helper.rb` — `token_endpoint_url`, `authorization_endpoint_url`, `refresh_token_endpoint_url`
- `config_helper.rb` — `config_is_set` (temporarily override config in a block)
- `authorization_request_helper.rb` — `resource_owner_is_authenticated`, `default_scopes_exist`

### Configuration Changes in Tests

Use `config_is_set` or the Doorkeeper.configure block (resets after each test):

```ruby
before do
  config_is_set(:access_token_expires_in, 100)
end
```

## Test Patterns

### Request/Flow Specs (most important)

```ruby
RSpec.describe "Authorization Code Flow" do
  let(:application) { FactoryBot.create(:application) }
  let(:resource_owner) { User.create!(name: "owner", password: "password") }

  before do
    default_scopes_exist :public
    resource_owner_is_authenticated resource_owner
  end

  it "issues an access token" do
    visit authorization_endpoint_url(client: application)
    click_on "Authorize"

    code = current_params["code"]
    post token_endpoint_url, params: {
      grant_type: "authorization_code",
      code: code,
      redirect_uri: application.redirect_uri,
      client_id: application.uid,
      client_secret: application.secret,
    }

    expect(response).to have_http_status(:ok)
    expect(json_response["access_token"]).to be_present
  end
end
```

### OAuth Unit Specs

```ruby
RSpec.describe Doorkeeper::OAuth::AuthorizationCodeRequest do
  subject(:request) do
    described_class.new(server, grant, client, params)
  end

  let(:server) do
    double :server,
           access_token_expires_in: 2.days,
           refresh_token_enabled?: false,
           custom_access_token_expires_in: lambda { |context|
             context.grant_type == Doorkeeper::OAuth::AUTHORIZATION_CODE ? 1234 : nil
           }
  end

  let(:resource_owner) { FactoryBot.create :resource_owner }
  let(:grant) do
    FactoryBot.create :access_grant,
                      resource_owner_id: resource_owner.id,
                      resource_owner_type: resource_owner.class.name
  end
  let(:client) { grant.application }
  let(:redirect_uri) { client.redirect_uri }
  let(:params) { { redirect_uri: redirect_uri } }

  before do
    allow(server).to receive(:option_defined?).with(:custom_access_token_expires_in).and_return(true)
  end

  describe "#authorize" do
    it "issues a new token for the client" do
      expect { request.authorize }.to change { client.reload.access_tokens.count }.by(1)
    end

    it "revokes the grant" do
      expect { request.authorize }.to(change { grant.reload.accessible? })
    end
  end

  describe "#validate" do
    it "requires the grant to be accessible" do
      grant.revoke
      request.validate
      expect(request.error).to eq(Doorkeeper::Errors::InvalidGrant)
    end

    it "requires the client" do
      request = described_class.new(server, grant, nil, params)
      request.validate
      expect(request.error).to eq(Doorkeeper::Errors::InvalidClient)
    end
  end
end
```

## What to Test

### For new features:
1. Happy path — the feature works as intended
2. Error cases — invalid input, missing parameters, unauthorized access
3. Edge cases — nil values, empty strings, boundary conditions
4. Configuration interaction — does it respect relevant config options?
5. Backward compatibility — does existing behavior still pass?

### For bug fixes:
1. Regression test — reproduce the exact bug scenario, verify it's fixed
2. Related edge cases — similar situations that might also be affected

### For security fixes:
1. The vulnerability is no longer exploitable
2. The fix doesn't break legitimate use cases
3. Error responses don't leak information

## Common Pitfalls

- **Don't use `sleep`** — use `Timecop.travel` or `Timecop.freeze` instead
- **Don't hardcode token values** — let the system generate them
- **Don't test private methods directly** — test through the public interface
- **Don't forget scopes** — many features are scope-dependent, set them up explicitly
- **Use `Timecop`** for time-dependent tests (expiration, token lifetime)

## Verification

1. Run new specs in isolation: `bundle exec rspec spec/path/to/new_spec.rb`
2. Run the full related directory: `bundle exec rspec spec/lib/oauth/`
3. Run full suite: `bundle exec rake spec`
4. Specs must pass in any order (`--order random`)

