テスト環境でsorceryを使用したユーザーログイン機能を使用したとき、Userクラスのインスタンスメソッドでハマった話

概要

テスト環境でsorceryを使用してログイン機能を追加した時、user.passwordで詰まった話。

失敗時のコード

以下のような、ユーザーが正常にログインできるかのテストコードを作成した時、テストに失敗した。

# spec/factories/users.rb

FactoryBot.define do
  factory :user do
    sequence(:email) { |n| "user_#{n}@example.com"  }
    password { 'password' }
    password_confirmation { 'password' }
  end
end
# spec/system/usersessions_spec.rb

require 'rails_helper'

RSpec.describe "UserSessions", type: :system do
  let(:user) { create(:user) }

  describe 'ログイン前' do
    context 'フォームの入力値が正常' do
      before do
        visit root_path
        click_link "Login"
        fill_in "Email", with: user.email
        fill_in "Password", with: user.password
        click_button "Login"
      end

      it 'ログイン処理が成功する' do
        expect(current_path).to eq root_path
        expect(page).to have_content "Tasks"
        expect(page).to have_content "Login successful"
      end
    end
  end
end

テストに失敗した時のスクリーンショットを見ると、ログインに成功してなかったみたいなのでclick_buttonの上にbinding.irbを挟んでuser.emailuser.passwordを確認してみると、以下のようにuser.passwordnilが返っていた。

irb(#<RSpec::ExampleGroups::UserSessions::Nested::Nested:0x00007f85a4104d08>):001:0> user.email
=> "user_1@example.com"
irb(#<RSpec::ExampleGroups::UserSessions::Nested::Nested:0x00007f85a4104d08>):002:0> user.password
=> nil

原因

何故こうなったかを調べると、usersテーブルにpasswordカラムが存在してないことが原因みたい。
sorceryを使用してログイン機能を追加する場合、usersテーブルにはcrypted_passwordカラムが存在して、仮想的な属性であるpasswordpassword_confirmationを元にcrypted_passwordカラムを作成している。なのでuser.passworduser.password_confirmationを指定してuser.createを実行すると、user.crypted_passwordが作成されてデータベースに保存されるが、仮想的な属性であるpasswordpassword_confirmationは無くなるみたいである。
今回の場合だとFactoryBot.create(:user)を使用すると、FactoryBotで指定したpasswordpassword_confirmationを値を元にuser.crypted_passwordを作成するのでuser.passworduser.password_confirmationは存在しなくなる(nilになる)。よって、ログインに失敗したようである。

解決方法

なので、今回のようにあらかじめ作成しておいたユーザーデータをもとにログインを検証するテストを作成する場合、fill_in "Password"の箇所をFactoryBotでpasswordに指定した"password"を直に書くことでうまくテストが通るようになった。

fill_in "Password", with: user.password
=> fill_in "Password", with: "password"