4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Qiita株式会社Advent Calendar 2023

Day 22

run_callbacks に翻弄された話

Last updated at Posted at 2023-12-21

これは何

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
4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?