belongs_toとhas_manyの関係にあるモデルオブジェクトをFactoryBotで作っていて、無限ループになってハマった話と対策。
モデル例
Groupに複数のUserが所属するものとする。モデルはこんな感じになる。
class Group < ApplicationRecord
has_many :users
end
class User < ApplicationRecord
belongs_to :group
end
これらをテストする時、FactoryBot.create(:user)とすれば、belongs_toなgroupも自動生成され、FactoryBot.create(:group)した時には、has_manyなuserもが自動生成されるになっているとテスト書くのが楽です。
belongs_to宣言しているモデルUserのFactoryBot
以下のようにbelongs_toの宣言そのまま書くだけでいい。
FactoryBot.define do
factory :user do
group
end
end
これでFactoryBot.create(:user)するとGroupのFactoryBotでGroupを作りUserとひもづけてくれる。
has_many宣言しているモデルGroupのFactoryBot
以下のようにするのが望ましいと思われる。
FactoryBot.define do
factory :group do
users {[
FactoryBot.build(:user, group: nil)
]}
end
end
ポイントは
- FactoryBot.createでなくbuildであること
- groupにnilを入れていること
である。
ここを、FactoryBot.create(:user)にしてしまうと、
- UserのFactoryBotもGroupを生成するようになっている
- つまりGroupのFactoryBotが呼び出される
- GroupのFactoryBotもUserを生成しようとする
- つまりUserのFactoryBotが呼び出される
- 最初に戻る
という無限ループになります。
よって、
- FactoryBot.build(:user)を使ってDBへの生成は行わずオンメモリで作る
- さらにgroupをnilを渡す。これによりGroup自身がDBにINSERTされ、Groupのidが決定した後に、そのidがusersのgroup_idに入る
となるように、FactoryBot.build(:user, group: nil)にしています。