😇

RSpecテスト内でログイン出来なくてハマった話

2023/05/04に公開

発生した問題

各要求に対して適切なHTTPステータスコードが正確に返されることを検証するテストを実行していたところ、ログインに関する問題が発生し、この問題によってテストが失敗する状況に直面しました。

該当コード

spec/requests/posts_request_spec.rb
require 'rails_helper'

RSpec.describe 'Postsコントローラーのテスト', type: :request do
  let(:user) { create(:user) }
  
  describe 'ログイン済み' do
    before do
      sign_in user
    end

    context 'GET #index' do
      it '200 OK' do
        get root_path
        expect(response.status).to eq 200
      end
    end
  end
  
  # ...
  
end
spec/factories/user.rb
FactoryBot.define do
  factory :user do
    name { Faker::Lorem.characters(number: 10) }
    email { Faker::Internet.email }
    password { 'password' }
    password_confirmation { 'password' }
    agreement { true }
  end
end
spec/rails_helper.rb
  # ...
  config.include FactoryBot::Syntax::Methods
  config.include Devise::Test::IntegrationHelpers, type: :request
end

問題発生

上記のテストコードを実行した際に、次のようなエラーが発生し、テストが失敗しました。

/myapp-backend# bundle exec rspec spec/requests
Failures:

  1) Postsコントローラーのテスト ログイン済み GET #index 200 OK
     Failure/Error: expect(response.status).to eq 200
     
       expected: 200
            got: 302
     
       (compared using ==)
     # ./spec/requests/posts_request_spec.rb:14:in `block (4 levels) in <top (required)>'

このエラーについて簡潔に説明すると、root_path自体は正しく見つかったものの(302 Found)、実際にそのパスへ遷移できていないという状況を示しています。

試したこと

まず、putsを使用して具体的なエラー内容を確認します。

spec/requests/posts_request_spec.rb
    # ...
    context 'GET #index' do
      it '200 OK' do
        get root_path
        puts "Response location: #{response.location}"
        expect(response.status).to eq 200
      end
    end
    # ...
Response location: http://www.example.com/users/sign_in

エラー内容から見ると、ログインが失敗して画面が切り替わっていないようです。
次に、ログイン失敗の原因を調査するために、Devise::Test::IntegrationHelpersから渡されるsign_inメソッドの内部動作を調べてみます。

https://github.com/heartcombo/devise/blob/4b72064bfcf076478c5c87818b9536b203f6584f/lib/devise/test/integration_helpers.rb#L37-L41

https://github.com/wardencommunity/warden/blob/88d2f59adf5d650238c1e93072635196aea432dc/lib/warden/test/helpers.rb#L18-L23

https://github.com/wardencommunity/warden/blob/88d2f59adf5d650238c1e93072635196aea432dc/lib/warden/proxy.rb#L170-L194

https://github.com/wardencommunity/warden/blob/88d2f59adf5d650238c1e93072635196aea432dc/lib/warden/manager.rb#L51-L53

ということでmanager.rbargsを調べた結果、

{:scope=>:user, :message=>:unconfirmed, :action=>"unauthenticated", :attempted_path=>"/"}

問題の原因がuserunconfirmed状態であることが判明しました。

unconfirmed状態とは、ユーザーがまだ確認メールを受信してアカウントを有効化していない状態を指します。この状態では、ログインが許可されていないため、テストが失敗しているようです。

解決

ユーザーのメール認証を完了させれば良いです。
以下に自分の知っている二通りの方法を示します。

方法1

spec/requests/posts_request_spec.rb
  # ...
  let(:user) { create(:user) }
  
  describe 'ログイン済み' do
    before do
+     user.confirm
      sign_in user
    end
    # ...

上のようにuser.confirmを加えることでユーザーのメール認証をスキップすることができます。

方法2

spec/factories/user.rb
FactoryBot.define do
  factory :user do
    name { Faker::Lorem.characters(number: 10) }
    email { Faker::Internet.email }
    password { 'password' }
    password_confirmation { 'password' }
    agreement { true }
+   confirmed_at { Time.now }
  end
end

もしくはユーザーの作成時に直接confirmed_atを指定してあげれば良いです。Deviseのメール認証機能はconfirmed_atカラムがnilかどうかで判定しているので、適当な日時で埋めてあげればメール認証完了になります。

終わりに

このエラー解消に半日くらいかかりました。ログイン関係のエラーは難しいです。

Discussion