みなさん、テストを書くときには Fixture Replacement として何を使ってますか?一番メジャーなところだと factory_girl でしょうか。machinist も有名ですね。シンタックスの違いのようなので基本的にはどちらでも良さそうです。

参考(stackoverflow)Machinist vs FactoryGirl - pros and cons
In other words, both are extremely similar, just with a different default syntax.

今回は(僕が factory_girl4.2.0 を使ってるので)factory_girl4.2.0 についての話です。

インストール


まずインストールしましょう。Rails を使っている場合であれば Gemfile に1行追加して bundle install するだけです。

group :test do
  gem 'factory_girl_rails', '~> 4.0'
end

さて、早速使ってみます。Blog と Article が1対多の関係だとしましょう。

使い方その1 belongs_to


Article を生成するときに、合わせて Blog も生成してみます。article belongs_to blog の関係です。

class Blog < ActiveRecord::Base
  has_many :articles
end

class Article < ActiveRecord::Base
  belongs_to :blog
end

FactoryGirlを定義します。

FactoryGirl.define do
  factory :article do
    blog
    title 'タイトル'
    body '本文'
  end

  factory :blog do
    title "sasata299's blog"
  end
end

このように指定すると、FactoryGirl.create(:article) とすることで、title がタイトル、body が本文という Article が1件生成されます。また、blog という指定で FactoryGirl.create(:blog) が自動的に呼ばれてそのIDが Article の blog_id に記録されます。

使い方その2 has_many


次は Blog を生成するときに Article を生成してみます。この場合は blog has_many articles の関係です。

FactoryGirl.define do
  sequence(:title) { |n| "タイトル-#{n}" }
  sequence(:body) { |n| "本文-#{n}" }

  factory :article do
    blog
    title
    body
  end

  factory :blog do
    title "sasata299's blog"

    after(:create) do |blog|
      3.times { create(:article, blog: blog) }
    end
  end
end

FactoryGirl.create(:blog) した際に after(:create) というシンタックスを使って、Blog の生成後に Article を3件生成しています。全部同じタイトル、本文だとアレなのでユニークなタイトル、本文にしてみました。

使い方その3 many_to_many


次はちょっとややこしい many_to_many の関係を考えてみましょう。Article に Tag を設定してみます。この場合、Article と Tag は many_to_many の関係ですね。中間テーブルとして article_tags テーブルを用意したとするとこんな感じ。

class Article < ActiveRecord::Base
  has_many :article_tags
  has_many :tags, through: :article_tags
end

class Tag < ActiveRecord::Base
  has_many :article_tags
  has_many :articles, through: :article_tags
end

class ArticleTag < ActiveRecord::Base
  belongs_to :article
  belongs_to :tag
end

で、FactoryGirlを定義します。

FactoryGirl.define do
  sequence(:title) { |n| "タイトル-#{n}" }
  sequence(:body) { |n| "本文-#{n}" }

  factory :article do
    blog
    title
    body

    after(:create) do |article|
      3.times do
        create(:article_tag, article: article, tag: create(:tag))
      end
    end
  end

  factory :tag do
    name 'タグ'
  end

  factory :article_tag do
    article
    tag
  end
end

これで、FactoryGirl.create(:article) すると、Article が生成された後、Tag が3件生成され、その中間テーブルの ArticleTag も同時に生成されます。

使い方その4 accepts_nested_attributes_for


最後は accepts_nested_attributes_for で Blog の生成時に Article もまとめて生成するような場合です。この場合には attributes_for というメソッドが役に立ちます。このメソッドは create 時に渡されているパラメータを返してくれるメソッドです。例えば Blog の場合、

# FactoryGirl.arrtibutes_for(:blog)
{ "title" => "sasata299's blog" }

このようなパラメータが返ります。

さて、具体的な例として Blog を生成するところの controller spec を考えてみます。

before do
  article_params = { articles_attributes: [FactoryGirl.attributes_for(:article), FactoryGirl.attributes_for(:article)] }
  post :create, blog: FactoryGirl.attributes_for(:blog).merge(article_params)
end

これでこのようなパラメータが渡されるわけですね。

{
  "blog" => {
    "title" => "sasata299's blog",
    "article_attributes" => [
      {
        "title" => "タイトル1",
        "body" => "本文1"
      },
      {
        "title" => "タイトル2",
        "body" => "本文2"
      }
    ]
  }
}

最後に


まだまだ他にも継承や Traits などいろいろ便利機能がありますが、この記事で紹介していることがわかっていればだいたい問題なく使えるんじゃないかと思います。

ちなみに spec/spec_helper.rb に以下の指定を書いておくと、毎回毎回 FactoryGirl と書かなくても良くなるので便利です。

# spec/spec_helper.rb にこれを指定しておくと
config.include FactoryGirl::Syntax::Methods

# これが
FactoryGirl.create(:user)
FactoryGirl.attributes_for(:user)

# こうなる
create(:user)
attributes_for(:user)

参考:factory_girl/GETTING_STARTED.md at master ・ thoughtbot/factory_girl
このエントリーをはてなブックマークに追加