はじめに
shoulda-matchersとは
Railsのテストコードをより簡潔かつ読みやすくするためのRSpec用の拡張ライブラリです。
Q. 何が良いのか?
A. 特に複雑なテストケースを1行で記述できることが多く、テストコードの行数の大幅な削減が期待できます!
設定方法
以下のようにGemfileのテスト環境にshoulda-matchers
を追加します。
(詳しくは公式ドキュメントにてご確認ください🙇♂️)
group :test do
gem 'shoulda-matchers', '~> 6.0'
end
今回の実装例
開発環境
言語(FW)など | バージョン | 最新ver(2024/8/11時点) |
---|---|---|
Ruby | 3.0.2 | 3.2.5 |
Rails | 6.1.4 | 7.1.3.4 |
Docker-Compose | 2.27.0 | 2.29.1 |
mysql | 8.0.25 | 8.0.38 |
補足
フロント側はVueを使用していますが、説明の関係上で省略しています🙇♂️
バリデーションの仕様
- メールアドレスが未入力だった時にバリデーションエラーを発生させる
- ユニーク(一意)な値でなければ、バリデーションエラーを発生させる
- メールアドレスが30文字を超えた場合にバリデーションエラーを発生させる
バリデーション
class User < ApplicationRecord
validates :email, presence: true,
uniqueness: { case_insensitive: false, message: :taken },
length: { maximum: 30 }
end
補足
case_insensitive
case_insensitive: false
の場合は大文字小文字を区別しないので、例えばtest@example.com
とTest@example.com
のような値は異なるメールアドレスとみなされ、両方登録できる。
エラーメッセージ
ja:
errors:
format: "%{attribute}%{message}"
messages:
taken: "はすでに存在します"
ja.ymlより抜粋
コントローラー
class UsersController < ApplicationController
def create
user = User.new(user_params)
if user.save
render json: user, status: :created
else
render json: { errors: user.errors.full_messages }, status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:email)
end
end
補足
errors.full_messages
今回は個人的にフロント側はVueで実装していたため、エラーメッセージをJSONで返しています。
例えばユニーク制約のバリデーションが発生した場合に、設定したuniqueness: { case_insensitive: false, message: :taken }
のtakenのエラーメッセージがuser.errors.full_messages
に格納されます。
RSpec
RSpec.describe User, type: :model do
describe 'email' do
subject { create(:user) }
it { is_expected.to validate_presence_of(:email) }
it { is_expected.to validate_uniqueness_of(:email).case_insensitive }
it { is_expected.to validate_length_of(:email).is_at_most(50) }
end
end
補足
-
subject
:テスト対象のオブジェクト(user)を定義します。subjectで定義することによって、オブジェクト名を繰り返し記述する必要がなくなります -
it
: RSpecのテストケースを定義するキーワードです -
is_expected.to
: テストの期待値を記述する際に使用します。こちらはexpect
よりも簡潔に記述するためにsubject
と一緒に使用されることが多いようです
is_expected.to
とexpect
の違いについてはこちら➡︎RSpec で subject & expect する時の注意点 -
validate_uniqueness_of(:email)
: モデルのemail属性が一意であることを検証するマッチャーです -
case_insensitive
: 大文字小文字を区別せずに一意性を検証するオプションです
shoulda-matchersを使わない場合
RSpec.describe User, type: :model do
describe "email" do
it "validates presence" do
user = User.new(email: "")
user.valid?
expect(user.errors[:email]).to include("メールアドレスが未入力です")
end
it "validates uniqueness (case-insensitive)" do
user1 = User.create(email: "test@example.com")
user2 = User.new(email: "TeSt@ExAmPlE.CoM")
user2.valid?
expect(user2.errors[:email]).to include("既に登録されているメールアドレスです")
end
it "validates length (maximum 50 characters)" do
user = User.new(email: "a" * 51)
user.valid?
expect(user.errors[:email]).to include("50文字以内で入力してください")
end
end
end
差は一目瞭然ですね...
Shoulda Matchersを使うことで、テストコードの重複を減らし、保守性を高めることができます。
テスト結果
% docker compose exec api rspec
.....................................................................
.....................................................................
.....................................................................
.....................................................................
..........................
Finished in 13.29 seconds (files took 7.46 seconds to load)
302 examples, 0 failures
まとめ
Shoulda Matchersは、RSpecのテストコードをより簡潔で読みやすくするためのライブラリです。
主にモデルのバリデーションテストなどで、マッチャーを使用することによってワンライナー(1行)でテストコードを簡潔に記述できます。