はじめに
既存Railsアプリにdeviseを導入した際に,
もともとあったRspecのテストを通すために苦労したので書いていきます。
バージョン
rails  : 6.1.1
ruby   : 2.6.6
devise : 4.7.3
原因
私がハマった原因は
- deviseのテストヘルパーを使うための設定に関する記述が間違っていたこと
- deviseのユーザー認証の仕組みをきちんと理解していなかったこと
です。
公式READMEのTest helpersの欄にもある通り、
deviseではtestで使える簡単にログイン・ログアウトができるヘルパーがあります。
書いてあるとおり、使えるようにするにはspec/rails_helper.rbに以下の記述をします。
RSpec.configure do |config|
  config.include Devise::Test::ControllerHelpers, type: :controller
  config.include Devise::Test::ControllerHelpers, type: :view
end
使い方はこんな感じ
require 'rails_helper'
RSpec.describe "Users" do
  it "ログインしてる時、ユーザーの詳細ページにアクセスできる" do
    user = create(:user)  
    sign_in user
    get users_path
    expect(response).to have_http_status(200)
  end
end
私はこれでtestのどこでもヘルパーが使えるようになると思っていたので、
何回やっても失敗するテストをみて首をかしげていました。
解決手順①
このヘルパーを上記のようなrequestテストでも本当に使えるようにはspec/rails_helper.rbに以下の記述を追加します。
RSpec.configure do |config|
  config.include Devise::Test::IntegrationHelpers, type: :request   #追加
  config.include Devise::Test::ControllerHelpers, type: :controller
  config.include Devise::Test::ControllerHelpers, type: :view
end
これでまずヘルパーがちゃんと使えるようになりました。
が、私の場合はこれだけではまだダメでした。
失敗したテストの内容とエラーをみてみます。
- テスト
require 'rails_helper'
RSpec.describe "Users" do
  let! (:user)  { create(:user) }
  let! (:user2) { create(:user) }
  describe '#edit' do
    context '本人ではない場合' do
      befor do  
        sign_in user2
        get edit_user_path user
      end
      it 'Homeにリダイレクトされる' do
        expect(response).to redirect_to root_path
      end
    end
  end
end
- エラー
Failure/Error: expect(response).to redirect_to root_path
     
       Expected response to be a redirect to <http://www.example.com/> but was a redirect to <http://www.example.com/users/sign_in>.
       Expected "http://www.example.com/" to be === "http://www.example.com/users/sign_in".
本人ではないユーザーがユーザー情報編集ページにアクセスすると、
Homeにリダイレクトされるということを確認するテストです。
エラーは、期待していたhttp://www.example.com/ではなく、
http://www.example.com/users/sign_inにリダイレクトされていますよ、と伝えています。
何故か。ログインページにリダイレクトされています。
解決手順②
原因はFactorybotの設定にありました。
今回はdeviseのconfirmableという
「ユーザー登録時に認証メールを送信し、メールに記載されているリンクにアクセスすることで本登録完了」
といった感じのよくある機能を提供してくれるモジュールを使用していました。
この機能は、ユーザーがメールのリンクにアクセスした時confirmed_atというカラムに認証の日時を保存し、その有無で機能へのアクセスを制限するというものです。
テストが通らなかった時のFactorybotの設定はこうでした。
FactoryBot.define do
  factory :user do
    name { "test_user" }
    sequence(:email) { |n| "test#{n}@exmple.com" }
    password { "password" }
    password_confirmation { "password" }
  end
end
confirmed_atカラムに関する記述がありません、これに書き加えます。
FactoryBot.define do
  factory :user do
    name { "test_user" }
    sequence(:email) { |n| "test#{n}@exmple.com" }
    password { "password" }
    password_confirmation { "password" }
    confirmed_at { Date.today }  ##追加
  end
end
これでテストが通るようになりました。
deviseに対する理解が全く足りていませんでした。
当たり前のことですが、使う前にきちんとREADMEなどを読み込んでおく事が重要だと身にしみました。
以上です。ありがとうございました。
参考記事
How To: sign in and out a user in Request type specs
devise
