Edited at

RSpecにおけるFactoryGirlの使い方まとめ

More than 3 years have passed since last update.

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