25
6

【RSpec】Shoulda Matchersの書き方について

Posted at

はじめに

shoulda-matchersとは

Railsのテストコードをより簡潔かつ読みやすくするためのRSpec用の拡張ライブラリです。

Q. 何が良いのか?

A. 特に複雑なテストケースを1行で記述できることが多く、テストコードの行数の大幅な削減が期待できます!

設定方法

以下のようにGemfileのテスト環境にshoulda-matchersを追加します。
(詳しくは公式ドキュメントにてご確認ください🙇‍♂️)

.Gemfile
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を使用していますが、説明の関係上で省略しています🙇‍♂️

バリデーションの仕様

  1. メールアドレスが未入力だった時にバリデーションエラーを発生させる
  2. ユニーク(一意)な値でなければ、バリデーションエラーを発生させる
  3. メールアドレスが30文字を超えた場合にバリデーションエラーを発生させる

バリデーション

user.rb
class User < ApplicationRecord

  validates :email, presence: true, 
            uniqueness: { case_insensitive: false, message: :taken }, 
            length: { maximum: 30 }
end

補足

case_insensitive

一意性制約で大文字小文字を区別するか、またはデータベースのデフォルトの照合順序(collation)を尊重すべきかどうかを定義できます。このオプションはデフォルトで、データベースのデフォルト照合順序を尊重します。

case_insensitive: falseの場合は大文字小文字を区別しないので、例えばtest@example.comTest@example.comのような値は異なるメールアドレスとみなされ、両方登録できる。

エラーメッセージ

ja.yml
  ja:
    errors:
      format: "%{attribute}%{message}"
      messages:
        taken: "はすでに存在します"

ja.ymlより抜粋

コントローラー

users_controller.rb
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

user_spec.rb
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.toexpectの違いについてはこちら➡︎RSpec で subject & expect する時の注意点
  • validate_uniqueness_of(:email): モデルのemail属性が一意であることを検証するマッチャーです
  • case_insensitive: 大文字小文字を区別せずに一意性を検証するオプションです

shoulda-matchersを使わない場合

user_spec.rb
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行)でテストコードを簡潔に記述できます。

参考記事

25
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
6