これは何
とあるModelのコールバックメソッドの中身のテストしようとしたが、FactoryBotの after(:build)
の存在に気付かず翻弄されたので、それをスキップする方法をまとめました。
-
after(:build)
- FactoryBot.build('hoge')が呼ばれた後に実行されるコールバック
きっかけとなったコード
Memberモデルに定義してある、assign_customer_id
メソッド内で正しくcustomer_idが設定されるかのテストを書いていました。
実際の動作テストでは成功しますが、RSpecのテストではcustomer_idが設定されずに成功しません。
尚、データ関連の生成には、基本的にはFactoryBotを使用するようにしています。
class Member < ApplicationRecord
# レコードが生成される直前に呼ばれるActiveRecordのコールバック
before_create :assign_customer_id
def assign_customer_id
# 外部APIと連携してcustomer_idをセットするような処理
end
end
describe '#assign_customer_id' do
subject do
member.save!
end
let(:member) do
build(:member)
end
it 'customer created' do
subject
# 正しくcustomer_idが設定されているかを確かめる処理
end
end
原因
今回テストしていたModelと対応するFactoryBot内で、assign_customer_id
のメソッドだけがモックに置き換わっているようでした。
そのため、FactoryBotを利用している限りは、絶対に assign_customer_id
の中身が実行されない作りになってました。
FactoryBot.define do
factory :member do
name { FFaker::Internet.user_name }
email { FFaker::Internet.email }
customer_id { '' }
transient do
stubs { [:assign_customer_id] }
end
after(:build) do |member, evaluator|
evaluator.stubs.each do |method|
# ここでモックに置き換えられている
receive = RSpec::Mocks::Matchers::Receive.new(method, -> {})
RSpec::Mocks::AllowanceTarget.new(member).to receive
end
end
end
end
記事ではさらっと書いていますが、頭の中にFactoryBotのコールバックに対する意識が抜けていたので、たどり着くまでに時間がかかりました。
※なぜモックになっているかは今回は説明を省きます。
対策
FactoryBotでデータを生成する際に、skip_after_build
を指定することで、after(:build)
をスキップするようにします。
-
trait
- FactoryBot内でのデータを定義を簡素にするために用いるオプション
-
transient
- 作成するデータと直接関係無い新しいattributeを定義する機能
- 挙動を変更するためのフラグ等に利用するのが一般的
FactoryBot.define do
factory :member do
name { FFaker::Internet.user_name }
email { FFaker::Internet.email }
customer_id { '' }
+ trait :skip_after_build do
+ # 実際に作成するデータと直接関係無い新しいattributeを定義する機能。
+ transient do
+ skip_after_build { true }
+ end
+ end
transient do
stubs { [:assign_customer_id] }
end
after(:build) do |member, evaluator|
+ # skip_after_buildが定義されていたら処理をスキップする
+ unless defined? evaluator.skip_after_build
evaluator.stubs.each do |method|
# ここでモックに置き換えられている
receive = RSpec::Mocks::Matchers::Receive.new(method, -> {})
RSpec::Mocks::AllowanceTarget.new(member).to receive
end
+ end
end
end
end
describe '#assign_customer_id' do
subject do
member.save!
end
let(:member) do
- build(:team)
+ build(:team, :skip_after_build)
end
it 'customer created' do
subject
# 正しくcustomer_idが設定されているかを確かめる処理
end
end
これで無事に書きたいテストが書けました。
割とレアなケースな気がするため、需要があるかはわかりませんが、少しでも参考になれば幸いです。