FactoryBotのbuildで関連モデルのレコードを生成する方法

2021-08-10

いつも混乱する FactoryBot の「あれ?」って思う部分を記事にいくつか書いていこうと思います 🙂

普通に name, email, password のシンプルな User モデルののテストデータを作るのなら、

FactoryBot.define do
  factory :user do
    name { Faker::Internet.username }
    email { Faker::Internet.email }
    password { 'password' }
  end
end

といった具合に作成できます。

しかし、こんな感じのモデル関係だった場合。

class User < ApplicationRecord
  belongs_to :group
end

class Group < ApplicationRecord
  has_many :users
end

Rails のアソシエーションは belongs_to の場合、カラムはデフォルトで必須項目(nil は不可)になっています。もしも上の例で group_id が nil で良いのならoptional: trueを書かなければいけません。

これを踏まえた上で、FactoryBot で書くと

FactoryBot.define do
  factory :user do
    name { Faker::Internet.username }
    email { Faker::Internet.email }
    password { 'password' }
    association :group
  end
end

FactoryBot.define do
  factory :group do
    name { Faker::Company.name }
  end
end

といった感じで書くと思います。これで簡単にモデルのテストを書こうとすると、

require "test_helper"

class UserTest < ActiveSupport::TestCase
  test "Userのレコードが生成できる" do
    user = FactoryBot.build(:user)
    assert user.save
  end
end

みたいな感じになります。

しかし、これはテストが通りません。FactoryBot.build(:user)で関連先のモデルが生成されないので、必須項目が埋まっていないというエラーが発生してしまいます 🤢

FactoryBot.create(:user)だと関連先のモデルも生成してくれるのですが、個人的にテストって.save.valid?で assert 書きたいんですよねー。

本来は外部キー制約がある物は紐付けられているレコードがあるべきだから、こうあるべきなんだろうけど、保存できない場合とかテストしたい時も関連モデルは作られていた方がやりやすいと思うんですよねー。

これを解決する方法は、

  1. 一括で build 時でも関連レコードを生成するオプションを使う
  2. 個別に build 時でも生成されるように設定する

の 2 つがあります ✌️

一括で build 時でも関連レコードを生成するオプションを使う

まず一括で build 時に生成する場合ですが、FactoryBot.use_parent_strategy = falseを必要なところで設定すれば、build 時でも関連レコードが生成されます。

※追記: これ、書いた後の FactoryBot すべてに適用されてしまうので、teardown で戻しておいてあげる必要があります 😓

class UserTest < ActiveSupport::TestCase
  teardown do
    FactoryBot.use_parent_strategy = true
  end

  test "Userのレコードが生成できる" do
    FactoryBot.use_parent_strategy = false
    user = FactoryBot.build(:user)
    assert user.save
  end
end

個別に build 時でも生成されるように設定する

factory ファイルの方で association を設定した時に、build でも生成するようにしておく事もできます。

FactoryBot.define do
  factory :user do
    name { Faker::Internet.username }
    email { Faker::Internet.email }
    password { 'password' }
    association :group, strategy: :create # <- ココ
  end
end

ただ、これは簡単に変えられないので、レコードの保存自体をテストする時だけ、上のFactoryBot.use_parent_strategy = falseの方が良いかもしれませんね 😆

それでは 🤟

参考