ざっくりまとめ
FactoryBotで、属性値にFakerを用いる場合、Dynamic Attributes という書き方を用いる。
つまりはこんな感じ。
FactoryBot.define do
factory :user, class: User do
email {Faker::Internet.email} //ここを{}で囲んでいる書き方がDynamic Attributes
name {Faker::Name.name} //上に同じ
end
end
背景
はじめに
Ruby on Railsのアプリケーションのテストコードを作成していました。Userモデルを生成し、FactoryBotとFakerでUserモデルのインスタンスを生成するためのファイルを生成しました。
app/models/user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
validates :name, :email, presence: true, uniqueness: true
end
spec/factories/users.rb
FactoryBot.define do
factory :user, class: User do
email Faker::Internet.email
name Faker::Name.name
(中略)
end
end
コードを動かしてみる
これでテストコードでuserインスタンスを生成するごとに、Fakerのおかげでインスタンス変数にランダムな文字列が割りあてられるはず....!!!
試しにrailsコンソールで、Factoryを用いてuserインスタンスを作成してみると、
(必要そうな部分のみ抜粋)
[1] pry(main)> FactoryBot.create(:user)
(0.2ms) BEGIN
SQL (123.3ms) INSERT INTO `users` (`email`, `encrypted_password`, `name`, `created_at`, `updated_at`) VALUES ('torrance@gorczanyjohnson.com', '$2a$11$kppvm7CCDvuj7JJEVPH8nugI1RwkYi9ElyuD/jKgLuQjQU9xtDoSe', 'Shad Kemmer','2018-01-03 17:54:21', '2018-01-03 17:54:21')
(40.1ms) COMMIT
=> #<User id: 55, email: "torrance@gorczanyjohnson.com", name: "Shad Kemmer", created_at: "2018-01-03 17:54:21", updated_at: "2018-01-03 17:54:21">
いい感じです。もう一回同じuserインスタンスを生成してみます。
[2] pry(main)> FactoryBot.create(:user)
(0.3ms) BEGIN
(11.8ms) ROLLBACK
ActiveRecord::RecordInvalid: Validation failed: Email has already been taken, Name has already been taken
バリデーションエラー...。 Userモデルでemailとnameカラムにかけたユニーク制約で引っかかっているようです。つまり、1回目に作成したインスタンスと、2回目に作成したインスタンスのemailとnameの値が同じになっているようです。
nameとemailの値はFakerで定義しているので、インスタンス生成ごとに異なる値で定義されてほしいですね。
原因を探る
FactorybotのReadmeを読んでみた
FactoryBotのReadme(Getting Started)を読んで見ると、こんな項目があった。
Dynamic Attributes
Most factory attributes can be added using static values that are evaluated when the factory is defined, but some attributes (such as associations and other attributes that must be dynamically generated) will need values assigned each time an instance is generated. These "dynamic" attributes can be added by passing a block instead of a parameter:
factory :user do
...
activation_code { User.generate_activation_code }
date_of_birth { 21.years.ago }
end
要約すると、
アソシエーションなどのコードをFactoryBotの属性値とする際、波括弧{}で属性値となる部分を囲むと、dynamic attributes として判断される。 また、dynamic attributesとして判断された属性値は、インスタンスを生成するたびに定義されるということです。
## ファイルを修正する
FactoryBotのReadmeにある通り、Fakerで定義しているemailとnameの値をDynamic Attributesとして定義します。
spec/factories/users.rb
```ruby
FactoryBot.define do
factory :user, class: User do
email { Faker::Internet.email }
name { Faker::Name.name }
(中略)
end
end
コードを動かしてみる
再度railsコンソールで、Factoryを用いてuserインスタンスを作成してみると、
[1] pry(main)> FactoryBot.create(:user)
(0.2ms) BEGIN
SQL (45.5ms) INSERT INTO `users` (`email`, `encrypted_password`, `name`, `profile`, `works`, `avatar`, `occupation`, `created_at`, `updated_at`) VALUES ('leie@will.net', '$2a$11$9.0f13f.z.Nq.y1n3bGA7exHcDwCOU6I5lXOM3ZqkpSWkLZFAA0GS', 'Cyril Hammes', '2018-01-03 18:34:42', '2018-01-03 18:34:42')
(11.7ms) COMMIT
=> #<User id: 56, email: "leie@will.net", name: "Cyril Hammes", created_at: "2018-01-03 18:34:42", updated_at: "2018-01-03 18:34:42">
[2] pry(main)> FactoryBot.create(:user)
(0.2ms) BEGIN
SQL (11.6ms) INSERT INTO `users` (`email`, `encrypted_password`, `name`, `created_at`, `updated_at`) VALUES ('neil.kreiger@lind.com', '$2a$11$SNdq09WdnRplpWe4Uxew.eU8jS/18JE5JKnnSKYCsxPq7.F8XqQ4y', 'Elsa Runolfsson', '2018-01-03 18:34:44', '2018-01-03 18:34:44')
(1.5ms) COMMIT
=> #<User id: 57, email: "neil.kreiger@lind.com", name: "Elsa Runolfsson", created_at: "2018-01-03 18:34:44", updated_at: "2018-01-03 18:34:44">
1回目と2回目のuserインスタンスを比較すると、emailとnameの値が異なっていることがわかります。Fakerが正常に動作していますね!!
ちなみに
FactoryBot Readmeの続きを読んでみると、こんなことも。
Dynamic Attributes
Because of the block syntax in Ruby, defining attributes as Hashes (for serialized/JSON columns, for example) requires two sets of curly brackets:
factory :program do
configuration { { auto_resolve: false, auto_define: true } }
end
factoryの属性値としてハッシュを用いる場合、波括弧{}2セットが必要ですよ! とのことでした。
# 動作環境
- ruby 2.4.0
- rails 5.0.1
- faker 1.8.7
- factory_bot_rails 4.8.2
# 参考
[FactoryBot Readme]
(https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md)