はじめに
今回はFactoryBotという存在は知っているけど、自分で作れって言われたら、ちょっと・・・と思う人に向けて、FactoryBotの作り方を記事にしました。
FactoryBotについての説明は割愛します。
FactoryBotの作り方
基本的な構文はこれです。
FactoryBot.define do
factory :user do
first_name { "John" }
last_name { "Doe" }
admin { false }
end
end
例えば、itemテーブル(商品)を作成するとしたら
- まず、FactoryBotの決まり文句を書きます。
FactoryBot.define do
factory :item do
end
end
- 次に、カラムを列挙します。
FactoryBot.define do
factory :item do
name {"レモン"} # {}で囲っているとfactoryが生成された時に評価される
price {3000}
end
end
使い方、属性の上書きの仕方
create(:item)
create(:item, name: "リンゴ")
アソシエーションの仕方
例えば、itemとuserが紐づいている場合、以下のように書く。
(userのfactoryBotも作成している必要がある)
FactoryBot.define do
factory :item do
name {"レモン"}
price {3000}
association :user, factory: :user # アソシエーション
end
end
association :user, factory: :user
の部分は単にuser
と省略できる
FactoryBot.define do
factory :item do
name {"レモン"}
price {3000}
user # アソシエーション
end
end
画像の紐付けの仕方
itemにimageが紐づいている時を想定します。
itemは必ずimageが紐づいており、imageは必ず紐づいたitemがあります。
先ほどの通り、実装すると、以下のようになりますが、imageを作るときに、itemが必要で、itemはまだ作成されていないので、結論うまくいきません。
# これはうまくいかない
FactoryBot.define do
factory :item do
name {"レモン"}
price {3000}
user
image
end
end
after(:build)を使用する必要があります。
これで、imageを一枚紐づけることができます。
FactoryBot.define do
factory :item do
name {"レモン"}
price {3000}
user
after(:build) do |item|
# carrierwaveの場合
item.images << build(:image)
# ActiveStorageの場合
item.images.attach(io: File.open('spec/fixtures/test_image.jpg'), filename: 'test_image.jpg', content_type: 'image/jpg')
end
end
end
# carrierwaveの場合のみ必要
FactoryBot.define do
factory :image do
image { File.open("#{Rails.root}/public/images/test_image.jpg") }
end
end
after(:build)で保存される直前に処理を実行でき、複数のfactoryを同時に生成することができる
Fakerの使い方
FactoryBotと良く一緒に使われるのがFaker。
Fakerを使うメリットは、ランダムな文字列を生成してくれるところにある。
例えば、deviseを用いたユーザログインでは、emailがすでに登録されていたらそのemailを使うことができない。
以下のようなFactoryを作成してしまうと
# これはうまくいかない
FactoryBot.define do
factory :user do
name {"テストユーザー"}
email {"test@hello.com"}
end
end
生成する際に、エラーが起こる
john = create(:user, name: "John")
kevin = create(:user, name: "kevin") # emailが重複して作成できない!!
Fakerを使用すると、ランダムに生成してくれて、エラーが起きない。
FactoryBot.define do
factory :user do
name {"テストユーザー"}
email { Faker::Internet.free_email }
end
end