FactoryBot関連付け注意点、余計なデータが作成されてしまう仕組み

概要

関連付け、複数のサンプルデータの定義の仕方 + 呼び出し方

前提条件

・gemインストール済み.
・FactoryBotサンプルデータの定義の仕方、呼び出し方がわかる.
・↓このブログの内容が理解できる(5分ほどで読めます.)

takishita0.hatenablog.com

FactoryBotの関連付け(注意点)

なお関連付けは以下の通りとする.
f:id:takishita0:20210221151553p:plain

図1.モデルER図


  • 余計なデータを作成してしまう例
    FactoryBot定義
spec/factories/
├── notes.rb
├── projects.rb
└── users.rb

#user.rb
FactoryBot.define do
  factory :user, aliases: [:owner] do
    first_name "Aaron"
    last_name  "Sumner"
    sequence(:email) { |n| "tester#{n}@example.com" }
    password "dottle-nouveau-pavilion-tights-furze"
  end
end

#project.rb
FactoryBot.define do
  factory :project do
    sequence(:name) { |n| "Project #{n}" }
    description "A test project."
    due_on 1.week.from_now
    association :owner
  end
end

#note.rb
FactoryBot.define do
  factory :note do
    message "My important note."
    #FactoryBot.create(:project)と等価
    association :project
    #FactoryBot.create(:user)と等価
    association :user
  end
end

noteに関するテスト

require 'rails_helper'

RSpec.describe Note, type: :model do
  it 'noteのサンプルデータをFactoryBotを使用して呼び出してみる' do
    note = FactoryBot.create(:note)
    #関連付けを使用してオブジェクトを呼び出してみる
    puts "#{note.project.user_id}"
    puts "#{note.user_id}"
  end
end

上のテストで期待することはどちらも同じuser_idが返ってくることです.

$ bin/rspec

Note
1
2

テスト結果からわかることは、userが2人分作成されていることです. なぜ2人作成されてしまうのでしょうか? コードを順に見ていきましょう.

_____________________________________________________________________
①
require 'rails_helper'

RSpec.describe Note, type: :model do
  it 'noteのサンプルデータをFactoryBotを使用して呼び出してみる' do
    note = FactoryBot.create(:note)
    #関連付けを使用してオブジェクトを呼び出してみる
    puts "#{note.project.user_id}"
    puts "#{note.user_id}"
  end
end
_____________________________________________________________________
②,③
#note.rb
FactoryBot.define do
  factory :note do
    message "My important note."
    #FactoryBot.create(:project)と等価
    association :project
    #FactoryBot.create(:user)と等価
    association :user
  end
end
____________________________________________________________________
④,⑤
#project.rb
FactoryBot.define do
  factory :project do
    sequence(:name) { |n| "Project #{n}" }
    description "A test project."
    due_on 1.week.from_now
    association :owner
  end
end

① FactoryBot.create(:note)が実行されると、spec/factories/notes.rbの内容が実行されます.
② message "My important note"が属性値として、代入されます.
③ association :projectでspec/factories/projectsが呼び出されます.つまりFactoryBot.create(:project)が実行されます.
④ name,description,due_onそれぞれカラムの値が設定されます.
⑤ association :ownerが実行されると、spec/factories/usersが呼び出されます.
つまりFactoryBot.create(:users)が実行され、一人目のuserが作成されました.(コードは省略します.)
※association :userではなく、association :ownerなのはモデルでuser_idではなく、owner_idになるように設定しているからです.
ここは今回の内容と関係ないので、association: ownerはassociation: userと同じことをしているという理解で大丈夫です.
下のコードが設定をしているコードです.

class Project < ApplicationRecord
  belongs_to :owner, class_name: 'User', foreign_key: :user_id
end

すこし脱線しましたが、②のコードをもう一度見てみましょう.
association :userが記述されています.
このコードが実行されると、userが当たらに作成されますよね.
しかしすでに、⑤によってuserは作成されています.そうです!これが2人のuserが作られてしまう仕組みです.
2人のユーザが作られないようにするには,したコードのようにすれば防げます.

#note.rb
FactoryBot.define do
  factory :note do
    message "My important note."
    #FactoryBot.create(:project)と等価
    association :project
    # 上のassociation :projectで作成されたユーザを渡してあげる
    user: { project.owner }
  end
end

テスト実行

$ bin/rspec

Note
1
1

期待通りになりました.

まとめ

FactoryBotを使用すると、気づかないうちに余計なデータが作成されてしまう.
余計なデータが作成されてしまうとテストに時間がかかるため、適宜チェックしよう.