これは何
Active Record のコールバックに関するテストのレビューをお願いされた時に、見慣れないメソッド にやや翻弄された話をまとめました。
きっかけとなったテストコード
Userモデルに定義してある、after_create_commit_test
メソッドがレコード生成時に正しく呼ばれているかをテストするために作られたものです。
describe '.create' do
subject { user.save! }
let(:user) { FactoryBot.create('user') }
before do
allow(user).to receive(:after_create_commit_test)
user.run_callbacks(:commit)
end
it 'calls after_create_commit_test' do
subject
expect(user).to have_received(:after_create_commit_test)
end
end
※FactoryBot
は テストデータの作成をサポートしてくれるライブラリです。
class User < ApplicationRecord
after_create_commit :after_create_commit_test
def after_create_commit_test
# NOP
end
end
after_create_commit
は Active Record のコールバックで、レコードが生成されてかつトランザクションが完了したら呼ばれるメソッドです。
見慣れないメソッド
-
run_callbacks
- 指定されたイベントのコールバックを実行します。
なんとなくは想像できてはいたけど、思った通りだった。
でも、レコードを作成したら自動的に呼ばれるのがコールバックなんだから強制的に呼んでもテストとして担保できないのでは?
試しに run_callbacks
を消してみた。
expected: 1 time with any arguments
received: 0 times with any arguments
1 example, 1 failure
after_create_commit_test
が呼ばれていないと失敗する。
まだ納得いっていないので、もう少し調べてみるとこんな記事を見つけた。
https://qiita.com/ogawatti/items/3ab6a86b9c881840fbb1
https://qiita.com/takeyuweb/items/e7261e9274b3b31d933c
通常、テスト中のデータベース操作はトランザクション内で行われ、コミットされません。
なるほど。
つまりテストでは、after_create_commit
そもそも呼ばれない仕組みになっており、
設定項目(use_transactional_fixtures
)を false にすることで解決できるらしい。
これで謎が解けたと思ったが、、、、、、、、、そもそもの設定が false でした。
RSpec.configure do |config|
config.use_transactional_fixtures = false
...
end
原因
割と長い時間色々試行錯誤していましたが、結局の原因は FactoryBot.create('user')
で、userが呼ばれた時点ですぐにレコードができてしまっており、receive(:after_create_commit_test)
が機能していませんでした。
# インスタンス生成だけする
user = FactoryBot.build(:user)
# インスタンス生成と同時にレコードに保存する
user = FactoryBot.create(:user)
FactoryBotの上記の仕様は知っていましたが、run_callbacks
に気を取られすぎて、完全に見落としておりました。
describe '.create' do
subject { user.save! }
let(:user) { FactoryBot.build('user') }
before do
allow(user).to receive(:after_create_commit_test)
end
it 'calls after_create_commit_test' do
subject
expect(user).to have_received(:after_create_commit_test)
end
end