factory_girlで快適なテスト生活


Rspecでの導入を想定して書いた記事になります。
ほとんどは[factory_girlのwiki](https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md)を参考にしてるのでwikiを読んだ人には不要かもしれません。
自分がまたfactory_girlを使うときに効率よくテストデータを作れるような再現性があるように頑張って書きます（意気込み）

## 導入
gemをインストールする。適したバージョンはGitHubを参考にしてほしい。

### テスト環境での設定
RSpecでは、以下のように設定する。

```rb:spec/support/factory_girl.rb
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に下記を追加。

```rb
require 'support/factory_girl'
```

これで準備完了。

## ファクトリ作成
ファクトリは下記のように作成できる。
クラス名で作ることもできるし、明示的な名前をつけることもできる。

```rb
# クラス名で作るパターン
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というメソッドを用意している。

```rb
# 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
```

どの使い方を使用しても、属性の上書きは可能。

```rb
user = build(:user, first_name: "Joe") # first_nameを上書きしている
user.first_name
# => "Joe"
```

## Transient
Transientは、一時的な属性を用意することでファクトリをDRYに記述する目的で使用する。

```rb
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"
```

## ネストしたファクトリ
ファクトリをネストさせると、同じ記述部分を繰り返すことなく複数のファクトリを作成することができる。

```rb
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にも最低限の属性を持った基本ファクトリを定義し、この基本ファクトリから継承した具体的なファクトリを作成するという流れが勧められていた。

```rb
factory :post do
  title "A title"
end

factory :approved_post, parent: :post do
  approved true
end
```

## アソシエーション
ファクトリ内でアソシエーションを定義する。
ファクトリ名とアソシエーション名が同じ場合は、ファクトリ名を省略できる。

```rb
factory :post do
  # ...
  author
end
```
属性の上書きも可能。

```rb
factory :post do
  # ...
  association :author, factory: :user, last_name: "Writely"
end
```

### アソシエーション先の扱い
親ファクトリのbuildやcreateによって、子ファクトリの状況が異なる。

```rb
# 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は属性をグループ化し、任意のファクトリに適用させることができる。

```rb
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は、属性としても使用できる。

```rb
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を使用するときは、第二引数が必ず作成したいインスタンスの数にすることに注意する。

```rb
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")
```

アソシエーションも楽に作成できる。

```rb

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
```

外部キー名とクラス名が異なる場合。

```rb
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を使用できる。

```rb
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も使用できる。

```rb
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
```