JWT認証つきAPIにJSON投げるrequest rspecを楽々書く

結論から書くとsupport黒魔術を使います。

前提

以下のコントローラーがあるとします。ここで ApiController は認証用ヘッダー Authorization: Bearer xxx... を要求するとします。

# frozen_string_literal: true

class Api::MessagesController < ApiController
  def index
    render json: params
  end

  def create
    render json: params
  end
end

このリクエストについて確認してくれそうなスペックを作成します。

回答

黒魔術を駆使した環境では、以下のように書けます[1]

# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Api::Messages', type: :request, auth: :user do
  let(:result) { JSON.parse response.body, symbolize_names: true }

  describe 'GET /api/messages' do'
    context 'sample' do
      it 'sample get with auth' do
        get '/api/messages', params: { boats: boats }
        expect(result).to include boats: ['a', 'b c', 'd'] 
      end
    end
  end

  describe 'POST /api/messages' do
    context 'sample' do
      it 'sample post with auth' do
        post '/api/messages', params: params, as: :json 
        expect(result).to include boats: ['a', 'b c', 'd']
      end
    end
  end
end

普通にget postしているだけですが、これで認証が通ります。headers の設定は一切ありません。快適ライフです。

呪文詳細

rspecを書くたびに current_user 作って、毎回アクセス用トークンを吐かせて get url, headers: { Authorization: ... } とかやってたら嫌なので、ここは黒魔術を使います。401確認用のテストも準備しておくと便利です(上のサンプルからは取り除かれていますが)。

# spec/support/auth_user_support.rb
# frozen_string_literal: true

module AuthUserSupport
  extend ActiveSupport::Concern

  included do
    let(:current_user) { FactoryBot.create :user }
    let(:current_user_token) { Knock::AuthToken.new(payload: current_user.to_token_payload).token }
    let(:header_authorization) { "Bearer #{current_user_token}" }

    # helper for auth check
    # define `subject` and add `it_behaves_like 'user auth api'`
    shared_examples 'user auth api' do
      context 'invalid auth token' do
        let(:current_user_token) { '' }
        before { subject }
        it { expect(response).to have_http_status(401) }
      end
    end

    prepend RequestHelpersCustomized
  end

  module RequestHelpersCustomized
    l = lambda do |path, **kwarg|
      default_headers = { Authorization: header_authorization }
      kwarg[:headers] = default_headers.merge(kwarg[:headers] || {})
      super(path, **kwarg)
    end
    %w[get post patch put delete].each do |method|
      define_method(method, l)
    end
  end
end

要するにrspec標準の get 他のメソッドを上書きして、勝手に認証ヘッダーを突っ込んでしまうわけです。やることはシンプル、やり方は強引。まあ楽勝ですね。

これを利用するために rails_helper.rb を弄ります。

# spec/rails_helper.rb

RSpec.configure do |config|
  config.include AuthUserSupport, auth: :user
end

ここで config.include AuthUserSupport, type: :request などと書くと、上記の「ヘッダー乗っ取り」処理が全リクエスト関連テストへ勝手に追加されます。この記述方法自体は問題なく動作しますが、大抵のシステムでは認証を必要としないAPIも存在するでしょうから、auth: :user などとして分けておく方が無難だと思います。

ちょろいもんだぜ!

この記事は、API設計が不味すぎてテストから全部書き直しと悟った筆者により「少しでも効率化せねば…」と苦し紛れに生み出されたテクニックに基づいています。


  1. 参考にした元ネタ ↩︎