テストのときのFixtureとして有名なfactory_girlの使い方をみてみましょう。
まずはModelが一つのときの使い方です。
class User < ActiveRecord::Base
attr_accessible :email, :name, :password, :password_confirmation
has_secure_password
end
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つあります。
- 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>
- 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>
- attributes_for
instanceを作成するのに必要な属性をハッシュにして返します
[3] pry(main)> user = FactoryGirl.attributes_for(:user)
=> {:name=>"John", :email=>"john@example.com"}
- 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)によって、モデルを作り分けたいとします。
class Story < ActiveRecord::Base
attr_accessible :author, :end_at, :published, :start_at, :title
belongs_to :author, :class_name => "User"
end
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つの使い方があります。
- 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>
- 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にはアクセスできません。