FactoryGirlの使い方でいまいちわかんない部分があったので
Getting Startedをみて必要なのだけピックアップしてみた
まとめというかただの和訳か。
ちなみにfakerなんかと組み合わせれば名前、アドレス、電話番号なんかをうまい具合に自動生成してくれるのでかなり便利です。
事前準備
gemインストール
Gemfileで以下書いてbundle install
# in Gemfile
# railsを使っている場合
gem "factory_girl_rails"
# railsを使っていいない場合
gem "factory_girl"
FactoryGirlの設定
railsならspec/spec_helper.rbに以下記述
これを書くとspecファイル内でFactoryGirlっていう接頭字を省略できる
# in spec/spec_helper.rb
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
end
使用方法
ファクトリーを定義する
ファクトリーはクラス毎に作る。
属性についてはクラス内の最低限の属性についてのみ値を定義する。
最低限というのは、バリデーションを通すのに必要なものという意味。
ちなみにファクトリ名は全ファイル内でユニークでないといけませんのでご注意。
FactoryGirl.define do
# クラス名=ファクトリ名
factory :user do
first_name "John"
last_name "Doe"
admin false
end
# クラス名とファクトリ名を変える場合はclassを明示的に指定
factory :admin, class: User do
first_name "Admin"
last_name "User"
admin true
end
end
定義したファクトリを利用する
以下のようにいろんな使い方ができる
# インスタンスを生成(ただし未保存)
user = build(:user)
# インスタンスを生成(保存済み)
user = create(:user)
# インスタンス生成に使われる属性集合を返す
attrs = attributes_for(:user)
# スタブオブジェクトを生成
stub = build_stubbed(:user)
# インスタンス生成のためにブロックを渡すパターン
create(:user) do |user|
user.posts.create(attributes_for(:post))
end
遅延評価属性を利用する
属性は、基本ファクトリ定義されたときに評価されるけど、
関連属性などのようにインスタンス生成時に動的に評価する必要があるものもある。
このようなものを遅延評価属性っていってパラメータの代わりにブロックを渡すことで定義できる
# ブロックを使って属性値を定義
factory :user do
# ...
activation_code { User.generate_activation_code }
date_of_birth { 21.years.ago }
end
他の属性を使って属性を定義する
他の属性値を使って新しく属性を定義することができる
# first_nameとlast_nameを使ってemail属性を定義
factory :user do
first_name "Joe"
last_name "Blow"
email { "#{first_name}.#{last_name}@example.com".downcase }
end
create(:user, last_name: "Doe").email
# => "joe.doe@example.com"
関連を定義する
ファクトリを使えば関連だって簡単に定義できちゃう。
factory :post do
# ファクトリ名を指定して関連を定義
author
# 上記に変えて、属性をオーバーライドして関連を定義
association :author, factory: :user, last_name: "Writely"
end
ただし、インスタンス生成の仕方によって挙動が異なる。
# インスタンス生成・保存を同時にやった場合
post = create(:post)
post.new_record? # => false
post.author.new_record? # => false
# インスタンス生成までの場合
post = build(:post)
post.new_record? # => true
post.author.new_record? # => false
上記については以下のように対処できる
factory :post do
# buildストラテジーを設定
association :author, factory: :user, strategy: :build
end
# インスタンス生成のみで保存までしていなくてもtrueになる
post = build(:post)
post.new_record? # => true
post.author.new_record? # => true
継承を使って拡張定義する
複数の似たようなファクトリを定義するのであれば継承を使うと差分拡張だけで定義できる
factory :post do
title "A title"
# approved属性のみ差分拡張定義(titleは継承)
factory :approved_post do
approved true
end
end
approved_post = create(:approved_post)
approved_post.title # => "A title"
approved_post.approved # => true
上記は入れ子定義する場合だが、そうじゃない定義の仕方もある。
factory :post do
title "A title"
end
# 親ファクトリを指定すれば入れ子にしなくてもよい
factory :approved_post, parent: :post do
approved true
end
連番を使って重複しないデータを定義する
data01,data02のように連番をもつデータなどを定義できる
FactoryGirl.define do
# sequenceで連番を定義
sequence :email do |n|
"person#{n}@example.com"
end
# こう書けば初期値も定義できる
sequence(:email, 100) do |n|
"person#{n}@example.com"
end
end
generate :email
# => "person1@example.com"
generate :email
# => "person2@example.com"
factory :user do
# こういう感じで利用する
email
# 遅延評価属性の場合はこう
invitee { generate(:email) }
end
属性をグループ化して使い分けることができる
traitsを使えばグループ化して適宜使い分けることができます。
factory :user, aliases: [:author]
factory :story do
title "My awesome story"
author
# published=trueのグループ
trait :published do
published true
end
# published=falseのグループ
trait :unpublished do
published false
end
# 週単位のpublishingのグループ
trait :week_long_publishing do
start_at { 1.week.ago }
end_at { Time.now }
end
# 月単位のpublishingのグループ
trait :month_long_publishing do
start_at { 1.month.ago }
end_at { Time.now }
end
# 週単位 ☓ published=true のファクトリ
factory :week_long_published_story, traits: [:published, :week_long_publishing]
# 突き単位 ☓ published=true のファクトリ
factory :month_long_published_story, traits: [:published, :month_long_publishing]
# 週単位 ☓ published=false のファクトリ
factory :week_long_unpublished_story, traits: [:unpublished, :week_long_publishing]
# 月単位 ☓ published=false のファクトリ
factory :month_long_unpublished_story, traits: [:unpublished, :month_long_publishing]
end
traitは属性としても使えますし、オーバーライドも可能です。
コールバックを活用する
以下の様なコールバックを活用できます
- after(:build) - インスタンス生成後に呼び出し
- before(:create) - インスタンス生成&保存前に呼び出し
- after(:create) - インスタンス生成&保存後に呼び出し
- after(:stub) - スタブオブジェクト生成後に呼び出し
以下例
factory :user do
after(:build) { |user| generate_hashed_password(user) }
end
コールバックは例えば、関連を定義する際に親インスタンス生成後のタイミングでコールバックを受けて子インスタンスを生成するときなんかに使うと良い
既存のファクトリ定義自体を書き換える
拡張定義などの他にファクトリ定義そのものを書き換えることも可能
たとえば、コールバックを使ってイベント後に書き換えるという感じで使う。
FactoryGirl.define do
factory :user do
full_name "John Doe"
sequence(:username) { |n| "user#{n}" }
password "password"
end
end
# ↑を↓をつかって書き換えることができる
FactoryGirl.modify do
factory :user do
full_name "Jane Doe"
date_of_birth { 21.years.ago }
gender "Female"
health 90
end
end
複数のインスタンスリストを生成する
一度に指定数のインスタンスリストを作ることも可能
# 25個のインスタンスを生成する
built_users = build_list(:user, 25)
created_users = create_list(:user, 25)
引数が必要なインスタンス生成を行う
new時に引数が必要な場合などにも対応可能
factory :user do
name "Jane Doe"
sequence(:email) { |n| "person#{n}@example.com" }
# initialize_withを使えば引数を使ってnewできる
initialize_with { new(name) }
# ファクトリ名と違うクラスのインスタンス生成の場合はクラスを明示
initialize_with { User.build_with_name(name) }
end
build(:user).name # Jane Doe