Rspecで多対多のFactoryを作ってSystemSpecでテストしたときのノート[RSpec, FactoryBot]


背景

Rspecで多対多の関係を作って、SystemSpecでテストをしました。
元々のテーブル構造の複雑さもあり、かなり苦労したので、以下にノートをまとめます。

実行完了は下記の通りです。

  • Rspec 3.9
  • Rails 5.2.4.2

テーブル構造

テーブル構造は、このようになっています。メインのテーブルは、offices(ある会社の支社)とshops(営業先である店舗)で、office(支社)はevebtを開いて、そこに営業先である店舗shopsが参加します。shopsは必ず何らかのcategoryを持っています。

起こっていた問題

モデルメソッドの関係で、どうしてもeventsfactoryを作った時に、それに紐づく、shopsが必要になりました。しかし、構造上、このように書いたのでは、エラーが出てしまいました。

let!(:shop){FactoryBot.create(:shop)}
let!(:event){FactoryBot.create(:event)}
#=> ここで eventに紐づくshopがないとエラーになる構造になっていた
let!(:event_shops){FactoryBot.create(:event_shop, event: event, shop: shop)}

対処方法

そこで、eventfactory生成時に、同時に関連するshopも定義できるようなfactoryを作成することにしました。

eventsのファクトリを作る

作成したFactoryは以下のようになっています。

spec/factories/events.rb
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_listshopのファクトリを複数作成でき流ようにし(ここでは1つしか必要ないので1つだけ)、eventsからeventのインスタンスが複数入れられるように、[event]と配列で表記します。

なお、categoryがわざわざtraitを使って呼び出してあるのは、後ほど解説します。

System Specで適切に呼び出せるようにする

spec/factories/categories.rb
FactoryBot.define do
  factory :category do
    name { "医療系" }

    trait :sequence do
      sequence(:id, 100)
      name { "服飾系" }
    end
  end
end
spec/system/events_spec.rb
let!(:event) { FactoryBot.create(:event, :with_shops, office: office, shops: [shop]) }
# ここからcategoryをshopに当てはめることはできない

次に、categoryのファクトリですが、ビューの中で、eventは複数回生成されます。そのため、同時に生成されるshopcateogoryは毎回別のid(FK)にしないと、FKの重複エラーになってしまいました。

また、system specからeventshopscategoryを当てはめようとすると、spec/factories/events.rb内で、shopのファクトリを作成しようとしたときに、「外部キーが存在しません」とエラーになってしまいました。

そのため、sequenceを利用して、一つ一つ違うid(FK)のcategoryを作成するようにしました。

終わりに(参考サイトなど)

解決に長い時間がかかってしまいましたが、なんとか解決できてよかったです!
今回はDBの構造が複雑だったため、このような複雑なSpecを書かねばならなくなってしまいましたが、
今後はDB設計をしっかりして、もっとシンプルなテストで済むようにしたいです。。。。

▼特に参考にさせていただいた記事など
- FactoryGirlで「多対多」や「複数の1対多」のアソシエーションを設定する
- Factorybotのtraitを使って、has_manyが2重にある複雑なassociation付きのデータを用意する
- FactoryBot (旧FactoryGirl) の sequence と .next