(EverydayRailsRSpecによるRailsテスト入門第4章)
・flateaでテストを読み書きする中で、FactoryBotを使ったデータの生成を理解するのに苦戦しました。
・flateaでテストを読み書きする中で、FactoryBotを使ったデータの生成を理解するのに苦戦しました。
→ これを機に基礎を理解したいと思い、
EverydayRailsRSpecによるRailsテスト入門第4章
「意味あるデータの作成」についてまとめることにしました。
①ファクトリ対フィクスチャ
②FactoryBotをインストールする
③アプリケーションにファクトリを追加する
④シーケンスを使ってユニークなデータを生成する
⑤ファクトリで関連を扱う
⑥ファクトリ内の重複をなくす
⑦コールバック
⑧ファクトリを安全に使うには
⑨まとめ
Railsにもともと備わっている、データを生成してくれる機能です。
・フィクスチャは比較的速い
・Railsに最初から付いてくる
・テストとは別のフィクスチャファイルに保存された値を覚えておく必要がある
・フィクスチャはもろくて壊れやすい(らしい)
・Railsはフィクスチャのデータを作るときActive Recordを読み込まない
ファクトリの方がオススメな理由
・ファクトリはシンプルで柔軟性に富んだテストデータ構築できる
・ファクトリは適切に使えば、テストをきれいで読みやすく、リアルなものに保ってくれる
しかし、多用しすぎると遅いテストの原因になることもある(らしい)
group :development, :test do
略
gem 'factory_bot_rails'
略
end
このような設定をしておくだけで、テスト内で FactoryBot.create(:user) と書くと、定義したuserのデータが生成されます。
FactoryBot.define do
factory :user do
name 'hayashi'
email "test@gmail.com"
password '11111111'
end
end
require 'rails_helper'
describe 'User' do
it '有効なファクトリを持つ事' do
expect(FactoryBot.create(:user)).to be_valid
end
end
end
一方、FactoryBotを使用せず書くと、以下のようになります。
require 'rails_helper'
describe 'User' do
it '有効なファクトリを持つ事' do
user = User.create(
name 'hayashi',
email "test@gmail.com",
password '11111111'
)
actexpect(:user).to be_valid
end
end
end
FactoryBotを使うと簡潔に書くことができる一方、どんな値を持ったデータが生成されているのかテストファイルを直接みるだけではわからないという注意点もあります。
時と場合によって、FactoryBotを使わずデータをベタ書きするのか、FactoryBotを使うのか使い分けるのが良いようです。
先ほど作ったファクトリを何回実行しても同じ値を持ったデータが作成されます。
メールアドレスのように、ユニークなデータでないとバリデーションエラーを起こしてしまう物に関しては、シーケンスを使ってユニークなデータを生成してあげることができます。
FactoryBot.define do
factory :user do
name 'hayashi'
sequence(:email) { |n| "test#{n}@gmail.com" }
password '11111111'
end
end
FactoryBotはアソシエーションを扱うときにとても便利です。
(例)
userモデル、projectモデル、noteモデルがあり、
・userモデルがhas_many :notes、has_many :projectsで、
・projectモデルがbelongs_to :user、has_many :notes
・noteモデルがbelongs_to :user、belongs_to :project
であるデータを作成する際は次のように作成できます。
FactoryBot.define do
factory :note do
message 'I am hayashi'
association :project
user {project.owner} // assosiation :user としてしまうと
end userが重複して作られてしまう
end
FactoryBot.define do
factory :project do
sequence(:name) { |n| "project#{n}" }
association :owner
end
end
FactoryBot.define do
factory :user, aliases: [:owner] do
name 'hayashi'
sequence(:email) { |n| "test#{n}@gmail.com" }
password '11111111'
end
end
あとは、以下のように、FactoryBot.create(:note) とnoteを作成すればOKです。
require 'rails_helper'
describe 'User' do
it '有効なファクトリを持つ事' do
expect(FactoryBot.create(:note)).to be_valid
end
end
end
ファクトリで、ある属性の値だけ別なファクトリを作りたい時は以下のように設定します。
こうすれば、
FactoryBot.create(:project)
FactoryBot.create(:project_due_yesterday)
とスペックでファクトリを呼び分けることで違うデータを持ったファクトリを生成することができます。
FactoryBot.define do
factory :project do
sequence(:name) { |n| "project#{n}" }
due_on 1.week.from_now
association :owner
end
factory :project_due_yesterday, class: Project do
sequence(:name) { |n| "project#{n}" }
due_on 1.day.from_now
association :owner
end
end
しかし、これだと、nameやassosiationの部分が重複しています。
この重複を無くした書き方も以下のようにすることで可能です。
factory :project の中に factory :project_due_yesterday を入れ子にすることで、重複していたnameやassosiationの記述、class: Projectの記述を省略できます。
FactoryBot.define do
factory :project do
sequence(:name) { |n| "project#{n}" }
due_on 1.week.from_now
association :owner
factory :project_due_yesterday do // ネストさせた
due_on 1.day.from_now
end
end
end
また、ファクトリをネストさせる他にも、traitを使った書き方もあります。
traitを用いて書いた時、スペックでファクトリを生成する際は、
FactoryBot.create(:project, :due_yesterday)
と引数でtraitを指定します。
FactoryBot.define do
factory :project do
sequence(:name) { |n| "project#{n}" }
due_on 1.week.from_now
association :owner
trait :due_yesterday do
due_on 1.day.from_now
end
end
end
あるモデルをcreate・newした後、別のモデルも作成したい場合はコールバックを使用します。
コールバックをtraitを用いて書けば、簡単にコールバックを呼んだり呼ばなかったり選択することができます。
こうしておくと、FactoryBot.create(:project, with_note) とすればコールバックが実施され、projectの関連先のnoteのデータも作成してくれます。
FactoryBot.create(:project) と通常通りならコールバックは実施されません。
これ以外にも、traitを使えば非常に複雑なデータの作成もスッキリかけるようになります。
FactoryBot.define do
factory :project do
sequence(:name) { |n| "project#{n}" }
due_on 1.week.from_now
association :owner
trait :with_note do
after(:create){|project| create_list(:note, 5, project: project)}
end
end
end
ファクトリを使うとテスト中に予期しないデータが作成されたり、無駄にテストが遅くなったりする原因になります。
(コールバックを使って関連するデータを作成する必要があるなら、ファクトリを使うたびに呼び出されないよう、トレイトの中でセットアップするようにするなど気をつけます。)
・ファクトリを使うことで、複雑なデータでもスペック内でスッキリと呼び出すことが可能になります。
・テストは理解しやすいものであることが大切なので、初心者の場合、ファクトリではなくnewやcreateでベタ書きするのもありだよ、と魚さんも言っていました。
・ファクトリの便利さを知れたので、まずは使い方をもっと理解し、パフォーマンスや可読性についても考慮してかけるようになりたいです。