LoginSignup
116
117

More than 5 years have passed since last update.

factory_girlの使い方

Posted at

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

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にはアクセスできません。

116
117
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
116
117