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


FactoryGirlの使い方でいまいちわかんない部分があったので
[Getting Started](https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md)をみて必要なのだけピックアップしてみた

まとめというかただの和訳か。

ちなみに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
```