【Rails5, Rspec】Deviseによる認証ありのAPIテスト


Rspec、特にrequest specにdevise認証判定を持たせたいときの記事です。

先人の方々が記事にはしてくれているのですが、Rails5になったりDeviseにもアップデートがあったりでいくらかは有益そうだったこと+自分の勉強のためにここに記します。

動いてみると当たり前ではありますが、冷静になってドキュメントを読まないと痛い目をみますね。

動作環境

rails 5.0.1
rspec 3.5.x
devise 4.2.0

RspecのIntegrationTest事情 with Devise

request specが結合テストに用いられるという記述や、capybaraが少し前までrequest spec推奨だったことなどから複雑に考えてしまいました。

RelishのRspecの項目とか公式のWikiのIntegrationTestの項目に以下のように記載がありました。

  • request specはRailsの統合テストについてのthinラッパーを提供する

Request specs provide a thin wrapper around Rails' integration tests, and are
designed to drive behavior through the full stack, including routing
(provided by Rails) and without stubbing (that's up to you).

  • capybaraを用いた結合テストにはDevise::Test::IntegrationHelpersではなくWarden::Test::Helpersを用いる

To do this, you'll need to include warden test helpers and turn on test mode.

ということでrequest specの場合DeviseのTestHelperは使えず、Warden::Test::Helpersにあるlogin_asを利用することになるかと思ったのですが、capybaraとか使ってない普通のAPIのテストであればDevise::Test::IntegrationHelpersにあるsign_inで対応可能でした。

実装

deviseのインストールやcontrollerへの権限設定は済んでいるものとしてrspec周りについて記します、また認証はuserモデルに付与しているものとしてください。テストデータはfactory girlで作ってます。

特に大きいmoduleでもないのですが、そのうち使いまわせるようになるという期待も込めてsupport以下にAuthenticattionMacrosとしてモジュール化しました。

support/authentication_macros.rb
module AuthenticationMacros
  def login
    let(:user) { build(:user) }
    before do
      sign_in user
    end 
  end
end

rails_helper.rbでヘルパーとモジュールの読み込み等を行います。

rails_helper.rb
#中略
require 'devise'
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
#中略
RSpec.configure do |config|
  config.include Devise::Test::IntegrationHelpers, type: :request
  config.extend AuthenticationMacros
#中略

最後に認証がかかったAPIに対するリクエストスペックです。

requests/pages_request_spec.rb
require 'rails_helper'

RSpec.describe 'Pages', type: :request do
  describe 'GET #index' do
    context 'with authentication' do
      login
      it 'return success status' do
        get '/pages'
        expect(response).to have_http_status(:success)
      end
    end
    context 'without authentication' do
      it 'return fail status' do
        get '/pages'
        expect(response).to have_http_status(302)
      end
    end
  end
end

所感

テストコードも肥大化してくると書くのが楽しくなりそうだなと思いました。
GitHubにコードを置いておいたので参考までにどうぞ!