Rspecでの導入を想定して書いた記事になります。
ほとんどはfactory_girlのwikiを参考にしてるのでwikiを読んだ人には不要かもしれません。
自分がまたfactory_girlを使うときに効率よくテストデータを作れるような再現性があるように頑張って書きます(意気込み)
導入
gemをインストールする。適したバージョンはGitHubを参考にしてほしい。
テスト環境での設定
RSpecでは、以下のように設定する。
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
end
# RSpec without Rails
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
config.before(:suite) do
FactoryGirl.find_definitions
end
end
サポートフォルダを読み込むように、rails_helperでsupport/factory_girl.rbを読み込むようにrails_helper.rbに下記を追加。
require 'support/factory_girl'
これで準備完了。
ファクトリ作成
ファクトリは下記のように作成できる。
クラス名で作ることもできるし、明示的な名前をつけることもできる。
# クラス名で作るパターン
FactoryGirl.define do
factory :user do
first_name "John" # ファクトリは`モデルのプロパティ 値`という形式でプロパティを持たせる。
last_name "Doe"
admin false
end
# ファクトリ名を自分で定義するパターン(ファクトリ名のあとにどのクラスのファクトリなのかを明示する必要あり)
factory :admin, class: User do
first_name "Admin"
last_name "User"
admin true
end
end
wikiには、クラスのインスタンスを作成するのに必要最低限の要素を持ったファクトリをクラスそれぞれに用意することを勧めているよ。
ファクトリの使用
ファクトリでは、build, create, attributes_forやbulid_stubedというメソッドを用意している。
# DBには保存しないでファクトリのインスタンスを作成する
user = build(:user)
# DBに保存し、ファクトリのインスタンスを作成する
user = create(:user)
# インスタンスを作成するのに必要な属性のハッシュを作成する
attrs = attributes_for(:user)
# スタブアウトされた属性のオブジェクトを作成する
stub = build_stubbed(:user)
# ブロック内の処理も行ったオブジェクトを作成する
create(:user) do |user|
user.posts.create(attributes_for(:post))
end
どの使い方を使用しても、属性の上書きは可能。
user = build(:user, first_name: "Joe") # first_nameを上書きしている
user.first_name
# => "Joe"
Transient
Transientは、一時的な属性を用意することでファクトリをDRYに記述する目的で使用する。
factory :user do
transient do
rockstar true
upcased false
end
name { "John Doe#{" - Rockstar" if rockstar}" }
email { "#{name.downcase}@example.com" }
after(:create) do |user, evaluator|
user.name.upcase! if evaluator.upcased
end
end
create(:user, upcased: true).name
#=> "JOHN DOE - ROCKSTAR"
ネストしたファクトリ
ファクトリをネストさせると、同じ記述部分を繰り返すことなく複数のファクトリを作成することができる。
factory :post do
title "A title"
factory :approved_post do
approved true
end
end
approved_post = create(:approved_post)
approved_post.title
# => "A title"
# A titleはpostに定義されていたものを使用している。
approved_post.approved
# => true
# approved: trueはapproved_postで定義したものを使用
同じ意味の記述をこうも書ける。
ファクトリがそれぞれ独立して見えるのでこっちのが好き。
wikiにも最低限の属性を持った基本ファクトリを定義し、この基本ファクトリから継承した具体的なファクトリを作成するという流れが勧められていた。
factory :post do
title "A title"
end
factory :approved_post, parent: :post do
approved true
end
アソシエーション
ファクトリ内でアソシエーションを定義する。
ファクトリ名とアソシエーション名が同じ場合は、ファクトリ名を省略できる。
factory :post do
# ...
author
end
属性の上書きも可能。
factory :post do
# ...
association :author, factory: :user, last_name: "Writely"
end
アソシエーション先の扱い
親ファクトリのbuildやcreateによって、子ファクトリの状況が異なる。
# UserもPostも保存される
post = create(:post)
post.new_record? # => false
post.author.new_record? # => false
# Postは保存されず、Userは保存される
post = build(:post)
post.new_record? # => true
post.author.new_record? # => false
# UserもPostも保存されない
factory :post do
# ...
association :author, factory: :user, strategy: :build # strategy: buildを定義する。
end
post = build(:post)
post.new_record? # => true
post.author.new_record? # => true
Trait
Traitは属性をグループ化し、任意のファクトリに適用させることができる。
factory :user, aliases: [:author]
factory :story do
title "My awesome story"
author
trait :published do
published true
end
trait :unpublished do
published false
end
trait :week_long_publishing do
start_at { 1.week.ago }
end_at { Time.now }
end
trait :month_long_publishing do
start_at { 1.month.ago }
end_at { Time.now }
end
factory :week_long_published_story, traits: [:published, :week_long_publishing]
factory :month_long_published_story, traits: [:published, :month_long_publishing]
factory :week_long_unpublished_story, traits: [:unpublished, :week_long_publishing]
factory :month_long_unpublished_story, traits: [:unpublished, :month_long_publishing]
end
Traitは、属性としても使用できる。
factory :week_long_published_story_with_title, parent: :story do
published
week_long_publishing
title { "Publishing that was started at #{start_at}" }
end
traitをcreate_list, build_listでも使用する。
*_listを使用するときは、第二引数が必ず作成したいインスタンスの数にすることに注意する。
factory :user do
name "Friendly User"
trait :admin do
admin true
end
end
# creates 3 admin users with gender "Male" and name "Jon Snow"
create_list(:user, 3, :admin, :male, name: "Jon Snow")
アソシエーションも楽に作成できる。
factory :user do
name "Friendly User"
trait :admin do
admin true
end
end
factory :post do
association :user, :admin, name: 'John Doe'
end
# creates an admin user with name "John Doe"
create(:post).user
外部キー名とクラス名が異なる場合。
factory :user do
name "Friendly User"
trait :admin do
admin true
end
end
factory :post do
association :author, :admin, factory: :user, name: 'John Doe'
# or
association :author, factory: [:user, :admin], name: 'John Doe'
end
# creates an admin user with name "John Doe"
create(:post).author
Traitの中でTraitを使用できる。
factory :order do
trait :completed do
completed_at { 3.days.ago }
end
trait :refunded do
completed
refunded_at { 1.day.ago }
end
end
Traitは、transientも使用できる。
factory :invoice do
trait :with_amount do
transient do
amount 1
end
after(:create) do |invoice, evaluator|
create :line_item, invoice: invoice, amount: evaluator.amount
end
end
end
create :invoice, :with_amount, amount: 2