thoughtbot/factory_girlを活用して日々テストを書いているRailsエンジニアの方々、こんにちは、正徳です。
Forkwellでもfactory_girlを使ってテストを書いています。 このfactory_girlには便利機能が多く、とても使いやすいのですが、女心並みに複雑*1なので、一通りの機能をブログにまとめてみました。
factory_girl初心者から、中級者の参考になれば幸いです。
参考にしたページ
このブログの内容は全てfactory_girlのGETTING_STARTEDに記載されています。
読まれた事のない方は、是非一読する事をおすすめします。
目次
- factory_girlのインストールと設定
- factory_girlの使い方(Using factories)
- 遅延評価(Lazy Attributes)
- 関連(Associations)
- 連番(Sequences)
- コールバック(Callbacks)
- 特性(Traits)
- 一時的な変数(Transient)
- 複数レコードの生成(Building or Creating Multiple Records)
- ActiveRecord以外での利用(Custom Construction)
factory_girlのインストールと設定
factory_girlをRailsで使う場合のインストール方法と設定を紹介します。
インストール
いつものように、Gemfileにfactory_girl_rails
を追加し、bundle install
します。
group :development, :test do gem "factory_girl_rails", "~> 4.0" end
本記事を書いた時のGETTING_STARTEDには"~> 4.0"
と書かれていましたが、最新のバージョンが変わっている可能性があります。
インストールする前に最新のバージョンを確認した方が良いです。
設定
GETTING_STARTEDに従って設定します。
# spec/support/factory_girl.rb RSpec.configure do |config| config.include FactoryGirl::Syntax::Methods end
spec/support/*
のファイルはデフォルトで読み込まれないため、rails_helper.rb
のコメントになっている箇所を修正します。
# spec/rails_helper.rb Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
factory_girlの使い方(Using factories)
user has_many jobs through favorites
というシンプルなモデル構造を例にして、FactoryGirlの説明をしていきます。
# app/models/user.rb class User < ActiveRecord::Base has_many :jobs, through: :favorites end # app/models/favorite.rb class Favorite < ActiveRecord::Base belongs_to :user belongs_to :job end # app/models/job.rb class Job < ActiveRecord::Base end
build
factoryの対象はDBに保存せず、 関連するレコードをDBに保存する という振る舞いのストラテジです。 バリデーションのテストなど、関連レコードがDBに保存されている必要があるテストで使うと良いです。
favorite = build :favorite #<Favorite id: nil, ...> favorite.user #<User id: 1, ...>
create
factoryの対象、関連レコードの 両方ともDBに保存する という振る舞いをするストラテジです。
where
などDBからデータを取得するようなテストで使用するのが良いです。
favorite = create :favorite #<Favorite id: 1, ...> favorite.user #<User id: 1, ...>
attributes_for
factoryの対象の属性を Hashで返す という振る舞いをするストラテジです。
user_attrs = attributes_for :user {:user_name=>"tshotoku", :admin=>true, :first_name=>"Shotoku", ...}
これはコントローラのテストでpostする時に便利です。
context 'with valid attributes' do before { post :create, user: attributes_for(:user) } it { expect(response).to be_redirect } end context 'with invalid attributes' do before { post :create, user: attributes_for(:user, first_name: nil) } it { expect(response).to render_template(:new) } end
build_stubbed
factoryの対象、関連レコードの両方とも DBに保存しない という振る舞いをするストラテジです。 DBのアクセスを必要とせず、インスタンスの属性だけでテストが可能な場合に使うと良いです。
ちなみに、idには適当な値が設定されます。
favorite = build_stubbed :favorite #<Favorite id: 1003, ...> favorite.user #<User id: 1001, ...>
遅延評価(Lazy Attributes)
属性に現在時刻やメソッド呼び出しなどを使う場合、遅延評価する必要があります。
factory :job do # ... published_at { Time.now } pending_mail_token { Job.generate_token } end
関連(Associations)
belongs_to
の関連とfactoryが同名の場合、シンプルに関連を定義することができます。
factory :job do company end
factoryと属性の名前が異なる場合、属性を上書きしたい場合などは下記のような指定も可能です。
factory :job do association :author, factory: :company, name: 'grooves' end
ストラテジの変更
build
を使う際に関連レコードをDBに保存したくない場合、ストラテジを指定することができます。
association :author, factory: :company, strategy: :build
後述する特性(Traits)と組み合わせることで、通常のテストでは関連レコードを作り、特定のテストでは関連レコードを作成しない、といった制御も可能です。
factory :job do association :author, factory: :company trait :with_new_author do association :author, factory: :company, strategy: :build end end job = build :job job.new_record? # => true job.author.new_record? # => false job_with_new_author = build :job, :with_new_author job_with_new_author.new_record? # => true job_with_new_author.author.new_record? # => true
連番(Sequences)
ユニークキー制約などで属性を変えたい場合、factory_girlによって連番を生成することができます。
factory :user do # person_1, person_2, person_3, ... sequence(:email) { |n| "person_#{n}@example.com" } end
ちなみに、初期値を変えたり、数値以外を使うこと*2も可能です。
初期値を変える例
factory :user do # person_1000, person_1001, person_1002, ... sequence(:email, 1000) { |n| "person_#{n}@example.com" } end
数値以外を使う例
factory :user do # person_a, person_b, person_c, ... sequence(:email, 'a') { |n| "person_#{n}@example.com" } end
コールバック(Callbacks)
下記4つのコールバックがあり、各処理の前後で特定の処理を行うことができます。
- before(:create) -
create
の実行前 - after(:build) -
build
,create
の実行後 - after(:create) -
create
の実行後 - after(:stub) -
build_stubbed
の実行後
factory :user do after(:build) { |user| generate_hashed_password(user) } end
特性(Traits)
複数の属性をグループ化し、factoryに適用できます。
factory :job do title 'Title' trait :published do published_at { Time.now } title { "Job published at #{published_at}" } end trait :unpublished do published_at nil end trait :general_salary_range do salary_lower 400 salary_upper 600 end trait :rich_salary_range do salary_lower 700 salary_upper 1_000 end end
Traitは組み合わせて使う事ができます。
job1 = create :job, :published, :general_salary_range #<Job id: 1, title: "Job published at 2015-04-28 12:03:28", published_at: "2015-04-28 12:03:28", salary_lower: 400, salary_upper: 600> job2 = create :job, :unpublished, :rich_salary_range #<Job id: 2, title: "Title", published_at: nil, salary_lower: 700, salary_upper: 1000>
一時的な変数(Transient)
本来のクラスには存在しないが、Factoryでのみ使いたい一時的な変数を定義することができます。 これはコールバックと組み合わせる時に便利です。
factory :user do transient do friends_count 0 end after(:create) do |user, evaluator| if evaluator.friends_count > 0 create_list :friendship, evaluator.friends_count, user: user end end end
複数レコードの生成(Building or Creating Multiple Records)
複数のレコードを生成したいときに使います。
built_users = build_list :user, 25 created_users = create_list :user, 25
また、create
などと同じく属性を上書きすることもできます。
# 10人の紳士が作られる gentlemen = create_list :user, 10, age: 30, gender: :man
ActiveRecord以外での利用(Custom Construction)
一般的なRubyのクラス、ActiveModelのクラスでもinitialize_with
を使うことでfactory_girlを利用することができます。
# app/models/user.rb class User attr_accessor :name def initialize(name) @name = name end end # spec/factories/users.rb factory :user do name "talkto_me" initialize_with { new name } end build(:user).name #<User name: "talkto_me">
ActiveModelのように、コンストラクタにハッシュを渡せる場合はattributes
を使うことも出来ます。
factory :user do name "talkto_me" age 17 initialize_with { new attributes } end build(:user) #<User name: "talkto_me", age: 17>