Ruby
Rails

factory_girlの使い方

More than 4 years have passed since last update.

テストのときのFixtureとして有名なfactory_girlの使い方をみてみましょう。
まずはModelが一つのときの使い方です。

https://github.com/thoughtbot/factory_girl

app/models/user.rb
class User < ActiveRecord::Base
  attr_accessible :email, :name, :password, :password_confirmation
  has_secure_password
end
spec/factories.rb
FactoryGirl.define do
  factory :user do
    name "John"
    email "john@example.com"

    factory :admin do
      admin true
    end

    factory :japanese_user do
      name "Tanaka"
    end
  end

  factory :another_user, class: User do
    name "Nash"
    email "nash@example.com"
  end
end

このようにmodelとfactoryを用意してコンソールを実行します。

$ bundle exec rails c test -s

属性を継承する(Inheritance)

factoryをネストして定義することで、属性値を継承したり、上書きしたりできます。
factoryがネストしていても、namaspaceのような働きをするわけではないので注意が必要です。

[1] pry(main)> FactoryGirl.build :user
=> #<User id: nil, name: "John", email: "john@example.com", created_at: nil, updated_at: nil, password_digest: nil, remember_token: nil, admin: false>
[2] pry(main)> FactoryGirl.build :admin
=> #<User id: nil, name: "John", email: "john@example.com", created_at: nil, updated_at: nil, password_digest: nil, remember_token: nil, admin: true>

:adminは:userにネストしているので、nameやemailの値を継承しています。

[3] pry(main)> FactoryGirl.build :japanese_user
=> #<User id: nil, name: "Tanaka", email: "john@example.com", created_at: nil, updated_at: nil, password_digest: nil, remember_token: nil, admin: false>

: japanese_userは:userにネストしているので、emailの値を継承しています。nameは上書きされて、"Tanaka"になっています。

[4] pry(main)> FactoryGirl.build :japanese_user, name: "Sato"
=> #<User id: nil, name: "Sato", email: "john@example.com", created_at: nil, updated_at: nil, password_digest: nil, remember_token: nil, admin: false>

なお、FactoryGirl.buildのときにハッシュを渡すことで、値を上書きできます。
この例ではnameを"Sato"に上書きしています。

factoryメソッドの第一引数をみて、Modelのクラスを判断しているため、

factory :another_user do
  name "Nash"
  email "nash@example.com"
end

のように書くと、AnotherUserモデルを使おうとして、エラーがでます。

[5] pry(main)> FactoryGirl.build :another_user
NameError: uninitialized constant AnotherUser
factory :another_user, class: User do
end

と書くことで使用するModelを指定できます。

[6] pry(main)> FactoryGirl.build :another_user
=> #<User id: nil, name: "Nash", email: "nash@example.com", created_at: nil, updated_at: nil, password_digest: nil, remember_token: nil, admin: false>

fixtureを作る(build strategies)

fixtureを作る方法は4つあります。

  1. build

instanceを作成しますが、DBには保存しません

[1] pry(main)> user = FactoryGirl.build(:user)
=> #<User id: nil, name: "John", email: "john@example.com", created_at: nil, updated_at: nil, password_digest: nil, remember_token: nil, admin: false>
  1. create

instanceを作成し、DBに保存します

[2] pry(main)> user = FactoryGirl.create(:user, password: "hogehoge")
   (0.4ms)  SAVEPOINT active_record_1
  User Exists (0.3ms)  SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER('john@example.com') LIMIT 1
  SQL (5.8ms)  INSERT INTO "users"...
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> #<User id: 1, name: "John", email: "john@example.com", created_at: "2014-04-12 11:42:52", updated_at: "2014-04-12 11:42:52", password_digest: "$2a$04$IQvpxha7ugL0fMU.AXpyY.8EFvWkp6pvJmGGNqEOll.S...", remember_token: nil, admin: false>
  1. attributes_for

instanceを作成するのに必要な属性をハッシュにして返します

[3] pry(main)> user = FactoryGirl.attributes_for(:user)
=> {:name=>"John", :email=>"john@example.com"}
  1. build_stubbed

stub用に必要な要素をすべて含んだオブジェクトを返します(buildとの違いがよくわからないので、後日調べます)。

[4] pry(main)> user = FactoryGirl.build_stubbed(:user)
=> #<User id: 1002, name: "John", email: "john@example.com", created_at: nil, updated_at: nil, password_digest: nil, remember_token: nil, admin: false>

トレイトを利用する (Traits)

traitを利用することで、属性を切り出して名前をつけることができます。例えばstoryモデルをテストするときに、公開されているか(publised)やどのくらいの期間公開されていたか(start_at, end_at)によって、モデルを作り分けたいとします。

app/models/story.rb
class Story < ActiveRecord::Base
  attr_accessible :author, :end_at, :published, :start_at, :title
  belongs_to :author, :class_name => "User"
end
spee/factories.rb
FactoryGirl.define do
  factory :user do
    name "John"
    email "john@example.com"

    factory :admin do
      admin true
    end

    factory :japanese_user do
      name "Tanaka"
    end
  end

  factory :another_user do
    name "Nash"
    email "nash@example.com"
  end
end

# 新しく追加
FactoryGirl.define do
  factory :story do
    title "My awesome story"
    association :author, factory: :user, strategy: :build

    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
end

このようにfactoryのブロック内で、traitを定義することで、そのfactoryに対して有効なtraitを作ることができます。traitには大きく2つの使い方があります。

  1. factory定義時に利用する
factory :week_long_published_story,    traits: [:published, :week_long_publishing]

のように書くことでtraitを組み込んだfactoryを定義することができます。

[1] pry(main)> story = FactoryGirl.build :month_long_unpublished_story
=> #<Story id: nil, title: "My awesome story", author_id: nil, published: false, start_at: "2014-03-12 05:50:26", end_at: "2014-04-12 14:50:26", created_at: nil, updated_at: nil>
  1. FactoryGirl.buildやFactoryGirl.create時に利用する

FactoryGirl.buildのときにtraitを渡すこともできます。

[2] pry(main)> story = FactoryGirl.build(:story, :week_long_publishing)
=> #<Story id: nil, title: "My awesome story", author_id: nil, published: nil, start_at: "2014-04-05 05:58:59", end_at: "2014-04-12 14:58:59", created_at: nil, updated_at: nil>
[3] pry(main)> story = FactoryGirl.build(:story, :week_long_publishing, :published)
=> #<Story id: nil, title: "My awesome story", author_id: nil, published: true, start_at: "2014-04-05 05:58:52", end_at: "2014-04-12 14:58:52", created_at: nil, updated_at: nil>

このあたりの使い方はテストが参考になります。
https://github.com/thoughtbot/factory_girl/blob/master/spec/acceptance/traits_spec.rb

ちなみにtraitは複数のfactoryで共有することもできます(有効な使い道はいまのところ分かりませんが・・・)。

FactoryGirl.define do
  trait :year_long_publishing do
    start_at { 1.year.ago }
    end_at { Time.now }
  end

  factory :user do
    name "John"
    email "john@example.com"
  end

  factory :story do
    title "My awesome story"
    association :author, factory: :user, strategy: :build

    trait :published do
      published true
    end
  end
end
[1] pry(main)> story = FactoryGirl.build(:story, :year_long_publishing)
=> #<Story id: nil, title: "My awesome story", author_id: nil, published: nil, start_at: "2013-04-12 06:17:04", end_at: "2014-04-12 15:17:04", created_at: nil, updated_at: nil>
[2] pry(main)> story = FactoryGirl.build(:user, :year_long_publishing)
NoMethodError: undefined method `start_at=' for #<User:0x007fb302c39710>

もちろんUserにはstart_atが定義されていないと怒られます。

[3] pry(main)> story = FactoryGirl.build(:user, :published)
ArgumentError: Trait not registered: published

他のfactroy内で定義されているtraitにはアクセスできません。