Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
750
Help us understand the problem. What is going on with this article?
@muran001

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

More than 5 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
750
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
muran001
2014年6月から大手SIerに見切りをつけてWeb系のエンジニアとして活動してます。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
750
Help us understand the problem. What is going on with this article?