FactoryBotを効果的に使用する
似た記事をQiitaに書いてますが、こっちをメインにする意味でも書き直そうと思います。
はじめに
ご存じの方も多いと思いますが、FactoryBotは主にテストを書く際に使用される、オブジェクトのインスタンス化を簡単にするためのものです。 ただ、FactoryBotは柔軟にオブジェクトを生成できてしまうため、よくメンテが辛かったり、何をテストしているのかがわからない状況になってしまうことが多くあるかと思います。 そうならないためのTipsをまとめようと思います。
外部キーを直指定しない
よくない例
FactoryBot.define do factory :post do title { 'FactoryBotを効果的に使用する' } user_id { 1 } end end
factoryの定義上で、 user_id
を 1
というマジックナンバーで定義してしまっています。
こうしてしまうと、このfactoryは id
が 1
のユーザーがいることが前提のfactoryになってしまい、テストをする際に id
が 1
のユーザーがいることを気にしないとダメなのはちょっと微妙ですね。
なので基本的にはマジックナンバーで指定するのではなく、別factoryを指定する方法があるのでそちらを使用しましょう。
改善例
FactoryBot.define do factory :user do name { 'ippachi' } end factory :post do title { 'FactoryBotを効果的に使用する' } user end end
このように書くと、factory :user
の定義を使用し、そのユーザーと紐付けてくれます。
参考: https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#associations
factoryに定義している値をテストで直接使用しない
よくない例
Class User scope :adult, lambda do where('users.age >= ?', 20) end end FactoryBot.define do factory :user do title { 'FactoryBotを効果的に使用する' } age { 20 } end end describe 'adult scope' do let(:user) { create(:user) } it 'returns a user' do expect(User.adult).to eq [user] end end
例では User
モデルの adult
というスコープのテストを書いています。
その中で、 age
が20以上であるユーザーを絞り込んでいるのですが、ユーザーの age
はfactoryで定義されています。
これはあまり良くありません。
例えば、このサービスのメインのユーザーが18歳であることがあとから判明した場合にfactoryの定義を18に変えたほうが、今後追加されるテストが書きやすくなることは想像できます。
ただそれをするとこのテストは当然ですが落ちます。
なので、テストに使う値は、同じものを上書きする形になったとしても、しっかりと指定するようにしましょう。
改善例
describe 'adult scope' do let(:user) { create(:user, age: 20) } it 'returns a user' do expect(User.adult).to eq [user] end end
複数回生成してもエラーにならないようにする
よくない例
FactoryBot.define do factory :user do email { 'test@example.com' } # unique end end describe 'example' do it 'test' do create(:user) create(:user) end end
例ではユーザーのメールアドレスを定義していますが、これはユニークですので複数回生成すると、DBエラーが吐かれます。
単にユーザーが複数欲しいとき、例えば、ユーザーのスコープのテストのときにそれぞれ指定するのは面倒です。
そういうときは sequence
を使用しましょう。sequence
は複数回factoryを作成した際にそのデータにインデックスを付けたいときなどに使用します。
改善例
FactoryBot.define do factory :user do sequence(:email) { |i| "test#{i}@example.com" } end end describe 'example' do it 'test' do create(:user) create(:user) end end
参考: https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#sequences
FactoryBotをモデルのUnitテストではあまり使用しないようにする
例えばモデルのUnitテストはrequestテストやE2Eテストと違い、そのモデルに注目したテストです。
そのテスト内でテスト対象のオブジェクトがどのように生成されたのかも大事な要素になります。
なのでfactoryを用いて生成せずに、できるだけ new
を使用して生成するようにしましょう。
他にもtraitを使うとかassociationなども色々ある気がしますが使い方を誤ると余計にメンテしづらくなるので、とりあえずこのくらいにしておきます。