背景
Rspecで多対多の関係を作って、SystemSpecでテストをしました。
元々のテーブル構造の複雑さもあり、かなり苦労したので、以下にノートをまとめます。
実行完了は下記の通りです。
- Rspec 3.9
- Rails 5.2.4.2
テーブル構造
テーブル構造は、このようになっています。メインのテーブルは、offices(ある会社の支社)とshops(営業先である店舗)で、office(支社)はevebtを開いて、そこに営業先である店舗shopsが参加します。shopsは必ず何らかのcategoryを持っています。
起こっていた問題
モデルメソッドの関係で、どうしてもeventsのfactoryを作った時に、それに紐づく、shopsが必要になりました。しかし、構造上、このように書いたのでは、エラーが出てしまいました。
let!(:shop){FactoryBot.create(:shop)}
let!(:event){FactoryBot.create(:event)}
# => ここで eventに紐づくshopがないとエラーになる構造になっていた
let!(:event_shops){FactoryBot.create(:event_shop, event: event, shop: shop)}
対処方法
そこで、eventのfactory生成時に、同時に関連するshopも定義できるようなfactoryを作成することにしました。
eventsのファクトリを作る
作成したFactoryは以下のようになっています。
FactoryBot.define do
factory :event do
office_id { nil }
name { "テストイベント" }
trait :with_shops do
after(:create) do |event|
category = FactoryBot.create(:category, :sequence)
create_list(:shop, 1, events: [event], category: category)
end
end
end
end
まず、eventのファクトリ内にwith_shopsというtraitを作成して、event作成時に関連するshopも作成できるようにします。create_listでshopのファクトリを複数作成でき流ようにし(ここでは1つしか必要ないので1つだけ)、eventsからeventのインスタンスが複数入れられるように、[event]と配列で表記します。
なお、categoryがわざわざtraitを使って呼び出してあるのは、後ほど解説します。
System Specで適切に呼び出せるようにする
FactoryBot.define do
factory :category do
name { "医療系" }
trait :sequence do
sequence(:id, 100)
name { "服飾系" }
end
end
end
let!(:event) { FactoryBot.create(:event, :with_shops, office: office, shops: [shop]) }
# ここからcategoryをshopに当てはめることはできない
次に、categoryのファクトリですが、ビューの中で、eventは複数回生成されます。そのため、同時に生成されるshopのcateogoryは毎回別のid(FK)にしないと、FKの重複エラーになってしまいました。
また、system specからeventのshopsにcategoryを当てはめようとすると、spec/factories/events.rb内で、shopのファクトリを作成しようとしたときに、「外部キーが存在しません」とエラーになってしまいました。
そのため、sequenceを利用して、一つ一つ違うid(FK)のcategoryを作成するようにしました。
終わりに(参考サイトなど)
解決に長い時間がかかってしまいましたが、なんとか解決できてよかったです!
今回はDBの構造が複雑だったため、このような複雑なSpecを書かねばならなくなってしまいましたが、
今後はDB設計をしっかりして、もっとシンプルなテストで済むようにしたいです。。。。
▼特に参考にさせていただいた記事など
