Rails FactoryBot入門:RSpecで使うテストデータの作り方まとめ
RSpec でテストを書くとき、テストデータを都度 User.create(name: "...") で作るのは手間がかかります。FactoryBot を使うと、モデルのデフォルトデータを一箇所に定義して create(:user) の一行で呼び出せます。
FactoryBot とは
テストデータのひな型(Factory)を定義するライブラリです。factory_bot_rails を使うと Rails / RSpec との連携が簡単にセットアップできます。
インストール・セットアップ
Gemfile の :test グループに追加します。
group :test do gem 'factory_bot_rails' end
bundle install
RSpec との連携設定
spec/support/factory_bot.rb を作成します。
RSpec.configure do |config| config.include FactoryBot::Syntax::Methods end
spec/rails_helper.rb で読み込みます。
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
これで RSpec 内で create / build をそのまま呼び出せます。
Factory の定義
spec/factories/ ディレクトリに置きます。ファイル名はモデル名の複数形が慣例です。
# spec/factories/users.rb FactoryBot.define do factory :user do name { "田中太郎" } email { "tanaka@example.com" } age { 25 } end end
{ } ブロックで値を定義するのがポイントです。文字列を直接渡すと全テストで同じ値になり、一意制約があるカラムでエラーになります。
sequence でユニークな値を生成する
FactoryBot.define do factory :user do sequence(:name) { |n| "ユーザー#{n}" } sequence(:email) { |n| "user#{n}@example.com" } age { 25 } end end
sequence を使うと呼び出すたびに n がインクリメントされ、一意な値を生成できます。
create / build / build_stubbed の使い分け
| メソッド | DB保存 | 用途 |
|---|---|---|
create(:user) |
する | 標準的な利用。DB 経由のロジックが必要なとき |
build(:user) |
しない | バリデーションや属性の確認だけするとき |
build_stubbed(:user) |
しない | DB に触れずに高速に動かしたいとき |
user = create(:user) # DB に保存される user = build(:user) # 保存されない(new した状態) user = build_stubbed(:user) # 保存されない + id が擬似的に設定される
テストが DB に依存しなくてよい場合は build や build_stubbed を使うとテストが速くなります。
RSpec での利用例
モデルスペック
# spec/models/user_spec.rb RSpec.describe User, type: :model do describe 'バリデーション' do context '正常なデータのとき' do it '有効であること' do user = build(:user) expect(user).to be_valid end end context 'name が空のとき' do it '無効であること' do user = build(:user, name: '') expect(user).not_to be_valid end end end end
属性を上書きするときは build(:user, name: '上書き値') のように引数で渡します。
リクエストスペック(コントローラー)
# spec/requests/users_spec.rb RSpec.describe 'Users', type: :request do describe 'GET /users/:id' do let(:user) { create(:user) } it '200 を返すこと' do get user_path(user) expect(response).to have_http_status(:ok) end end describe 'POST /users' do context '正常なパラメーターのとき' do it 'ユーザーが作成されること' do expect { post users_path, params: { user: attributes_for(:user) } }.to change(User, :count).by(1) end end end end
attributes_for(:user) を使うと Factory に定義した属性をハッシュで取得できます。フォームのパラメーターとして渡すときに便利です。
trait でバリエーションを定義する
管理者ユーザーや停止ユーザーなど、状態の違うデータを作りたいときは trait を使います。
FactoryBot.define do factory :user do sequence(:name) { |n| "ユーザー#{n}" } sequence(:email) { |n| "user#{n}@example.com" } age { 25 } role { :general } trait :admin do role { :admin } end trait :suspended do suspended_at { Time.current } end end end
呼び出し方:
create(:user) # 通常ユーザー create(:user, :admin) # 管理者ユーザー create(:user, :suspended) # 停止ユーザー create(:user, :admin, :suspended) # trait は複数組み合わせ可能
RSpec の context と組み合わせると読みやすくなります。
context '管理者ユーザーのとき' do let(:user) { create(:user, :admin) } it '管理画面にアクセスできること' do # ... end end
association で関連モデルを作る
Post が User に属している場合、Factory に association を定義すると関連モデルを自動生成できます。
# spec/factories/posts.rb FactoryBot.define do factory :post do title { "サンプル投稿" } body { "本文テキスト" } association :user end end
post = create(:post) post.user # => 自動生成された User が入っている
既存のユーザーに紐付けたいときは属性で渡します。
user = create(:user) post = create(:post, user: user)
まとめ
| 操作 | 書き方 |
|---|---|
| Factory 定義 | spec/factories/モデル名の複数形.rb |
| DB に保存して作成 | create(:user) |
| DB に保存せず作成 | build(:user) |
| 属性を上書き | create(:user, name: '別名') |
| trait 指定 | create(:user, :admin) |
| ハッシュで取得 | attributes_for(:user) |
| 関連モデルを定義 | association :user |
| ユニーク値を生成 | sequence(:email) { |n| "user#{n}@example.com" } |
sequenceを使わないと一意制約エラーになりやすい- DB が不要なテストでは
build/build_stubbedを使うとテストが速くなる traitで状態のバリエーションを定義しておくとcontextと相性がよい
RSpec の基本については「RSpec入門:インストールからモデルスペックの書き方まで」も参照してください。