こんにちは、かつコーチです。
「テストは大事」と分かっていても、何をどこまで書けばいいのか迷った経験はありませんか。
この記事では、RSpecの導入からモデルのバリデーション・アソシエーションのテストの書き方までを整理します。
FactoryBotを使ったテストデータの準備方法にも触れるので、実務レベルのモデルテストを書けるようになりましょう。
rspec-railsの導入手順
Gemfileへの追加
rspec-railsは開発・テスト環境でのみ必要なので、:development, :testグループに追加します。
# Gemfile
group :development, :test do
gem "rspec-rails"
gem "factory_bot_rails"
end
追加したらインストールし、RSpecの初期設定を生成します。
bundle install
rails generate rspec:install
このコマンドで、以下のファイルが生成されます。
.rspec(RSpecの実行オプション設定)spec/spec_helper.rb(RSpec本体の設定)spec/rails_helper.rb(Rails関連の設定を読み込む)
rails_helperの基本設定
spec/rails_helper.rbには、テストDBの自動マイグレーションチェックなどが最初から記述されています。
FactoryBotを使う場合は、以下の1行を追加しておくと便利です。
# spec/rails_helper.rb
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
end
この設定により、テストコード内でFactoryBot.create(:user)ではなくcreate(:user)と短く書けるようになります。
モデルのバリデーション・アソシエーションのテスト例
バリデーションのテスト
まずは対象となるモデルを用意します。
# app/models/article.rb
class Article < ApplicationRecord
belongs_to :user
validates :title, presence: true, length: { maximum: 100 }
validates :body, presence: true
end
このモデルに対するテストは、spec/models/article_spec.rbに書きます。
# spec/models/article_spec.rb
require "rails_helper"
RSpec.describe Article, type: :model do
describe "バリデーション" do
it "titleが空だと無効であること" do
article = build(:article, title: nil)
expect(article).not_to be_valid
expect(article.errors[:title]).to include("を入力してください")
end
it "titleが100文字を超えると無効であること" do
article = build(:article, title: "あ" * 101)
expect(article).not_to be_valid
end
it "titleとbodyが揃っていれば有効であること" do
article = build(:article)
expect(article).to be_valid
end
end
end
buildはDBへの保存を行わず、メモリ上にインスタンスを生成します。
保存を伴わないバリデーションのテストでは、createより高速なbuildを使うのが基本です。
アソシエーションのテスト
アソシエーションが正しく機能しているかは、関連レコードの取得結果で確認します。
# spec/models/article_spec.rb
RSpec.describe Article, type: :model do
describe "アソシエーション" do
it "userに紐付いていること" do
user = create(:user)
article = create(:article, user: user)
expect(article.user).to eq(user)
end
end
end
shoulda-matchersgemを導入すると、アソシエーションの定義自体をより簡潔にテストできます。
# Gemfile
group :test do
gem "shoulda-matchers"
end
# spec/models/article_spec.rb
RSpec.describe Article, type: :model do
it { should belong_to(:user) }
it { should validate_presence_of(:title) }
end
コード量が減る一方、内部で何を検証しているか分かりにくくなる面もあります。
チームの習熟度に応じて使い分けるとよいでしょう。
FactoryBotでテストデータを準備する
buildやcreateを使うには、事前にFactoryを定義しておく必要があります。
# spec/factories/articles.rb
FactoryBot.define do
factory :article do
title { "テスト記事のタイトル" }
body { "テスト記事の本文です。" }
association :user
end
end
# spec/factories/users.rb
FactoryBot.define do
factory :user do
sequence(:email) { |n| "user#{n}@example.com" }
password { "password123" }
end
end
sequenceを使うことで、一意制約のあるemailカラムでも重複エラーを避けられます。
FactoryBotの詳しい使い方は別記事で扱いますが、まずはこの最小構成を覚えておけば十分です。
よくあるつまずきポイント・エラー対処
一意制約テストでのハマりどころ
❌Before
# spec/models/user_spec.rb
it "emailが重複していると無効であること" do
create(:user, email: "test@example.com")
user = build(:user, email: "test@example.com")
expect(user).not_to be_valid
end
このテストを実行したところ、意図通りにパスしました。
しかし別の開発者が同じテストファイルに追記したところ、次のエラーが出るようになりました。
ActiveRecord::RecordInvalid: Validation failed: Email has already been taken
原因は、Factoryのsequenceを使わず固定のemailを直書きしていたため、他のテストと値が衝突していたことでした。
✅After
# spec/models/user_spec.rb
it "emailが重複していると無効であること" do
existing_user = create(:user)
user = build(:user, email: existing_user.email)
expect(user).not_to be_valid
end
既存レコードのemailを再利用する形にすることで、Factory側のsequence定義とテストコードの整合性を保てました。
一意制約を検証するテストでは、固定値の直書きを避けるのが安全です。
まとめ
この記事のポイント
rspec-railsはgem "rspec-rails"追加後、rails generate rspec:installで導入する- バリデーションのテストは、DB保存を伴わない
buildを基本にする - アソシエーションのテストは、実際のレコード生成か
shoulda-matchersで検証する - FactoryBotの
sequenceは、一意制約のあるカラムのテストで重複を避けるために使う - 固定値の直書きは、他のテストとの衝突原因になりやすい
次に読むべき記事
- System SpecでE2Eテストを書く
- バリデーションの基本
タグ: Ruby on Rails, 中級者向け, テスト