---
title: "shoulda-matchersの使い方【8.0対応】｜設定・主要マッチャー・バージョン選定"
url: "https://www.issoh.co.jp/tech/details/3359/"
published: 2024-08-21
updated: 2026-10-12
categories: ["Ruby"]
publisher: "株式会社一創"
---

# shoulda-matchersの使い方【8.0対応】｜設定・主要マッチャー・バージョン選定

shoulda-matchersは、Railsモデルのバリデーションやアソシエーションが宣言どおり定義されているかを1行で検証できるgemです。thoughtbot社が開発し、2026年6月12日に最新の8.0.1がリリースされました。8.0系はRuby 3.3以上・Rails 7.2以上が必須で、それより古い環境では別のバージョンを指定する必要があります。この記事では、Gemfileへの追加からrails\_helper.rbの設定、バリデーションとアソシエーションの実際の書き方、バージョンの選び分け、そして初見では原因がわかりにくい`ExistingRecordInvalid`エラーの対処までを、公式リポジトリのソースとリリースノートに基づいて整理します。

## まとめ

- 最新は8.0.1（2026年6月12日）です。Gemfileの`test`グループに`gem 'shoulda-matchers', '~> 8.0'`を書き、`bundle install`を実行します。
- 設定はrails\_helper.rb末尾に`Shoulda::Matchers.configure`ブロックを1つ置くだけです。追加のrequireは要りません。
- 8.0系の必須要件はRuby 3.3以上・Rails 7.2以上です。Ruby 3.2やRails 7.1が残っているなら7.0.1、Rails 6.1〜7.0なら6.5.0を指定します。
- 主戦場はモデルテストです。`validate_presence_of`や`belong_to`のような宣言の検証に絞って使います。
- `validate_uniqueness_of`だけは内部で既存レコードを作るため、NOT NULL制約のある列があると`ExistingRecordInvalid`で落ちます。`subject`に有効なレコードを渡して回避します。
- 業務ロジックのテストには向きません。公式READMEも、フレームワーク側の設定漏れを防ぐ道具として位置づけています。

ここから先は、実際のコードとバージョン情報で1つずつ確認していきます。

## shoulda-matchersが肩代わりする記述と素のRSpecとの差

### バリデーション1件あたり5行から1行への短縮

Railsのモデルに`validates :name, presence: true`と書いたとき、その宣言が効いているかを素のRSpecで確認しようとすると、インスタンスを作って属性をnilにして`valid?`を呼び、エラーメッセージを検証する、という手順になります。

```
# 素のRSpecで書いた場合
RSpec.describe User, type: :model do
  it 'name が必須であること' do
    user = User.new(name: nil)
    user.valid?
    expect(user.errors[:name]).to include("can't be blank")
  end
end

# shoulda-matchers を使った場合
RSpec.describe User, type: :model do
  it { should validate_presence_of(:name) }
end
```

5行が1行になります。ただし検証している内容は同じではありません。前者はエラーメッセージの文言まで確認しているのに対し、後者は「presenceバリデーションが宣言されていること」だけを確認します。属性が10個・20個と増えるモデルほど、後者の記述量削減が効いてきます。RSpec自体の考え方から確認したい場合は[RSpecとは何か？テスト駆動開発におけるRSpecの役割と概要](/tech/details/3993/)を先に読むと、以降のコードが追いやすくなるはずです。

### RSpecとMinitestの両対応、マッチャーが効く範囲

gem名に`should`が入っているためRSpec専用と誤解されがちですが、Minitestでも同じマッチャーが使えます。書き方は`it { should ... }`が`should ...`に変わる程度です。

一方、マッチャーが提供される範囲はActiveModel・ActiveRecord・ActionController・ルーティング、そして`delegate_method`のような単独マッチャーに限られます。ブラウザ操作を伴うE2Eテストや、HTTPリクエストを通した結合テストは対象外です。前者は[RSpecでCapybaraとPlaywrightを導入するための基本手順](/tech/details/6355/)、後者は[リクエストスペックとは？基本概念と目的についての説明](/tech/details/3993/)で扱う領域になります。

## インストールとRSpec設定：Gemfile追加からrails\_helper.rbの記述まで

### Gemfileのtestグループへの追加

公式READMEが示す記述は、testグループ限定での追加です。開発環境や本番環境に読み込ませる必要はありません。

```
# Gemfile
group :test do
  gem 'shoulda-matchers', '~> 8.0'
end
```

記述後に`bundle install`を実行します。制約を`'~> 8.0'`と書くと8.0以上9.0未満が対象になり、8.0.1のようなパッチ更新は自動で取り込まれます。注意したいのはバージョン指定を省略した場合です。BundlerはRubyのバージョン要件に合わせて解決するため、Ruby 3.2の環境では8.0系が候補から外れ、7.0系が入ります。エラーにはならず古いマッチャーで動いてしまうので、バージョンは必ず明示してください。

### Railsアプリでの設定（rails\_helper.rb末尾）

gemを入れただけではマッチャーは使えません。どのライブラリのマッチャーを、どのテストフレームワークに組み込むかを設定ブロックで宣言します。Railsアプリの場合はspec/rails\_helper.rbの末尾に次を置きます。

```
# spec/rails_helper.rb の末尾
Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :rails
  end
end
```

`with.library :rails`は、ActiveModel・ActiveRecord・ActionController・ルーティングの4つをまとめて有効化する指定です。ソース上も`SUB_LIBRARIES`としてこの4つが列挙されています。この設定ブロックを書き忘れると`undefined method 'validate_presence_of'`のようなNoMethodErrorになります。導入直後にマッチャーが認識されない場合は、まずこのブロックの有無と配置場所を確認してください。

### Rails以外のプロジェクトでの設定

Railsを使わずActiveRecordやActiveModelだけを利用しているプロジェクトでも導入できます。この場合は`:rails`ではなく、必要なライブラリを個別に指定してspec/spec\_helper.rbの末尾に置きます。

```
# spec/spec_helper.rb の末尾（Rails を使わない場合）
Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec

    # 使う分だけ残す
    with.library :active_record
    with.library :active_model
  end
end
```

非Rails環境ではもう一点、注意が要ります。Railsプロジェクトではexample groupのタグに応じてマッチャーが自動で読み込まれますが、非Rails環境ではその仕組みが働きません。`include Shoulda::Matchers::ActiveRecord`のようにモジュールを明示的にincludeするか、RSpec.configureで`config.include(Shoulda::Matchers::ActiveModel, type: :model)`と指定します。

### Minitestで使う場合のtest\_helper.rb

Minitest環境でも設定の形は同じで、test\_helper.rbの末尾に置きます。

```
# Gemfile
group :test do
  gem 'shoulda-matchers', '~> 8.0'
  gem 'shoulda-context', '~> 2.0'   # context, should 記法が必要なら
end

# test/test_helper.rb の末尾
Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :minitest
    with.library :rails
  end
end
```

公式READMEはumbrella gemのshoulda経由を案内していますが、2026年8月時点のshouldaは4.0.0（2020年6月13日）が最新で、依存が`shoulda-matchers ~> 4.0`に固定されています。shoulda経由では8.0系は入りません。8.0系のマッチャーを使いたいMinitestプロジェクトでは、上のようにshoulda-matchersを直接指定し、`context`と`should`の記法が必要ならshoulda-contextを単体で追加してください。

## バージョン選定：Ruby・Railsの対応要件と8.0系の破壊的変更

### 8.0.1時点の対応バージョン早見表

shoulda-matchersはメジャーバージョンごとに旧Rails・旧Rubyのサポートを切っていく方針です。各バージョンのREADMEが明示している対応関係は次のとおりです。

| バージョン | リリース日      | 対応Ruby | 対応Rails |
| ----- | ---------- | ------ | ------- |
| 8.0.1 | 2026-06-12 | 3.3以上  | 7.2以上   |
| 7.0.1 | 2025-10-31 | 3.2以上  | 7.1以上   |
| 6.5.0 | 2025-04-25 | 3.0以上  | 6.1以上   |
| 4.5.1 | –          | 2.4以上  | 4.2以上   |

Rails 7.2以上かつRuby 3.3以上なら8.0系、Rails 7.1が残っているかRuby 3.2で止まっているなら7.0.1、Rails 6.1〜7.0なら6.5.0を選びます。これより古い環境には、Ruby 2.4未満・Rails 4.1未満向けにv3.1.3という案内先も残っています。

### 8.0.0で削除されたRails 7.1・Ruby 3.2サポート

8.0.0のリリースノートは、後方互換性のない変更としてRails 7.1とRuby 3.2のサポート終了を明記しています。8.0系へ上げる際の最大の障壁がこれです。

記法の面でも1つ落とし穴があります。`define_enum_for`に`with_options`をチェーンしていたコードは、8.0.0から`NotImplementedError`で落ちるようになりました。ソースには「with\_options is not a valid qualifier for the define\_enum\_for matcher. Did you mean to use with\_values instead?」という例外メッセージが実装されています。7.x系ではActiveSupportの`Object#with_options`に吸われて素通りしていた記述なので、8.0系へ上げる前に`with_values`へ置き換えてください。逆にいえば、この1点と実行環境の要件さえ満たせば、既存のテストコードはほぼそのまま動きます。

### 8.0系で追加された複数属性の一括検証

8.0.0の新機能で実務に効くのが、1つのマッチャーへ複数の属性をまとめて渡せるようになった点です。属性ごとに`it`を並べる必要がなくなりました。

```
class Person
  include ActiveModel::Model
  attr_accessor :first_name, :last_name

  validates_presence_of :first_name, :last_name
  validates_length_of :first_name, :last_name, maximum: 50
end

RSpec.describe Person, type: :model do
  # 8.0 以降は複数属性をまとめて渡せる
  it { expect(subject).to validate_presence_of(:first_name, :last_name) }

  # チェーンした修飾子は渡した全属性に一律で適用される
  it { expect(subject).to validate_length_of(:first_name, :last_name).is_at_most(50) }
end
```

対応しているのはActiveModelの`validate_*`系9個に、`have_readonly_attribute`と`have_one_attached`を加えた11個です。修飾子は渡した全属性に同じ条件として適用されるため、属性ごとに条件が違う場合はまとめられません。いずれかの属性で失敗した場合、失敗メッセージには落ちた属性とその理由が属性ごとに列挙されます。

## モデルテストの書き方：バリデーションとアソシエーションの記述例

### バリデーション系マッチャーの基本形

モデルテストで最も使うのがActiveModelのバリデーション系マッチャーです。`validates`で宣言したものに対応するマッチャーが用意されています。

```
RSpec.describe User, type: :model do
  describe 'validations' do
    it { should validate_presence_of(:email) }
    it { should validate_length_of(:name).is_at_most(50) }
    it { should validate_numericality_of(:age).is_greater_than_or_equal_to(0) }
    it { should validate_inclusion_of(:role).in_array(%w[admin member guest]) }
    it { should validate_confirmation_of(:password) }
    it { should have_secure_password }
  end
end
```

`has_secure_password`を宣言したモデルでパスワードのpresenceを検証する場合は注意が要ります。`subject`にパスワードが設定済みのレコードを渡すと、「passwordが設定済みのuserに対してvalidate\_presence\_ofが呼ばれた。空のpasswordを持つuserを使ってほしい」という趣旨のエラーになります。`has_secure_password`がpasswordをnilにできない形にモデルを上書きするためで、マッチャー側の不具合ではありません。

### アソシエーション系マッチャーとオプション指定の検証

ActiveRecordのアソシエーションは、関連の有無だけでなくオプションまで検証できます。修飾子をチェーンする形です。

```
RSpec.describe Post, type: :model do
  describe 'associations' do
    it { should belong_to(:user) }
    it { should belong_to(:category).class_name('PostCategory').optional }
    it { should belong_to(:organization).counter_cache(true) }
    it { should have_many(:comments).dependent(:destroy) }
    it { should have_many(:tags).through(:taggings).source(:tag) }
    it { should have_one(:profile).inverse_of(:post) }
    it { should have_and_belong_to_many(:groups) }
    it { should have_one_attached(:thumbnail) }
  end
end
```

マッチャー名が`have_and_belong_to_many`と単数形である点に注意してください。モデル側の宣言は`has_and_belongs_to_many`で複数形です。写し間違えるとNoMethodErrorになります。

チェーンできる修飾子は`class_name`、`with_foreign_key`、`dependent`、`through`、`source`、`counter_cache`、`inverse_of`、`optional`、`required`、`validate`、`autosave`、`touch`、`order`、`index_errors`、`strict_loading`などです。`counter_cache(true)`のようにオプション値まで固定できるため、キャッシュカラムの設定漏れを検知できます。counter\_cache自体の実装とカウントずれの対処は[counter\_cacheとは？Railsでの実装手順とカウントずれ・後付けの対処](/tech/details/3727/)、関連の種類そのものの整理は[RailsのAssociationとは？](/tech/details/2883/)にまとめています。

### subjectの上書きとFactoryBotでの有効レコード用意

バリデーションのテストでは、まっさらな`Model.new`ではなく有効なレコードを`subject`に渡すのが定石です。公式READMEも「迷ったらテスト対象クラスのインスタンスを渡す」と明記しています。

```
RSpec.describe Post, type: :model do
  describe 'validations' do
    # FactoryBot で必須属性が埋まったレコードを用意する
    subject { build(:post) }

    it { should validate_presence_of(:title) }
  end
end
```

この形にしておくと、次章で扱う`ExistingRecordInvalid`も同時に避けられます。`subject`を上書きしない場合、RSpecはdescribeに渡したクラスの`new`を暗黙のsubjectとして使います。必須属性が多いモデルほど、この暗黙のsubjectが原因で落ちるケースが増えるわけです。

## validate\_uniqueness\_ofでExistingRecordInvalidが出る原因と回避手順

`validate_uniqueness_of`は、他のマッチャーと動作原理が違います。一意性は「既に同じ値のレコードが存在するとき」にしか検証できないため、このマッチャーはまずDBに既存レコードがあるかを調べ、無ければ自分でINSERTします。そのINSERTが、テスト対象と無関係な列のNOT NULL制約に引っかかって失敗する、というのが典型的なつまずきです。

```
class CreatePosts < ActiveRecord::Migration[7.2]
  def change
    create_table :posts do |t|
      t.string :title
      t.text :content, null: false   # ← これが原因になる
    end
  end
end

class Post < ActiveRecord::Base
  validates :title, uniqueness: true
end

# この書き方は例外で落ちる
RSpec.describe Post, type: :model do
  it { should validate_uniqueness_of(:title) }
end
```

このとき出るのは通常のテスト失敗ではなく例外です。PostgreSQLなら次のようなメッセージになります。

```
Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher::ExistingRecordInvalid:
  validate_uniqueness_of works by matching a new record against an
  existing record. If there is no existing record, it will create one
  using the record you provide.

  While doing this, the following error was raised:

    PG::NotNullViolation: ERROR:  null value in column "content" violates
    not-null constraint
    DETAIL:  Failing row contains (1, null, null).

  The best way to fix this is to provide the matcher with a record where
  any required attributes are filled in with valid values beforehand.
```

末尾の1文がそのまま対処法です。必須属性を埋めたレコードを事前に用意し、`subject`として渡します。前章の`subject { build(:post) }`のようにFactoryBot経由でも構いませんし、ファクトリが無ければ直接組み立てても同じです。

```
RSpec.describe Post, type: :model do
  describe 'validations' do
    subject { Post.new(content: 'Here is the content') }

    it { should validate_uniqueness_of(:title) }
  end
end
```

複合ユニーク制約を張っている場合は`scoped_to`で表現します。マッチャー側だけ付けても通らないので、モデルの宣言と対になっているかを確認してください。

```
# モデル側が scope 付きで宣言されているときだけ有効
class Post < ActiveRecord::Base
  belongs_to :category
  validates :title, uniqueness: { scope: :category_id }
end

RSpec.describe Post, type: :model do
  subject { Post.new(content: 'Here is the content', category: Category.new) }

  it { should validate_uniqueness_of(:title).scoped_to(:category_id) }
end
```

MySQLやSQLiteでもドライバ由来のメッセージが変わるだけで、原因と対処は同じです。DBスキーマ側にNOT NULLを増やしたタイミングで、一見無関係な一意性テストが突然落ち始めるのはこの挙動によります。制約を追加するPRでは、一意性テストのsubjectがファクトリ経由になっているかを併せて確認しておくと再発を防げます。

## コントローラ・ルーティングのマッチャーとsubjectで壊れる書き方

### permit・filter\_param・ルーティングの検証

ActionController向けにも、Strong Parametersやコールバック、ルーティングの宣言を検証するマッチャーがあります。

```
RSpec.describe PostsController, type: :controller do
  it { should permit(:title, :body).for(:create, params: { post: { title: 'a' } }) }
  it { should use_before_action(:authenticate_user!) }
  it { should filter_param(:password) }
end

RSpec.describe 'routes for Posts', type: :routing do
  # routing グループではコントローラを明示する
  it { should route(:get, '/posts').to(controller: :posts, action: :index) }
end
```

ただし、このうちコントローラスペックを積極的に使う場面は限られます（ルーティングスペックはこの指摘の対象外です）。rspec-railsのREADMEが「RSpec 3.5以降、RailsチームとRSpecチームのいずれも、リクエストスペックのような機能テストを優先し、コントローラを直接テストすることを推奨していない」と明記しているためです。コントローラ系マッチャーは、`permit`や`filter_param`のようにリクエストスペックでは確認しづらいコントローラ固有の宣言に絞るのが現実的な使い方になります。

### subjectではなくbeforeを使うべきケース

コントローラスペックで`subject`にリクエストを入れる書き方は、一部のマッチャーを壊します。公式READMEが挙げている例が分かりやすいでしょう。

```
RSpec.describe PostsController, type: :controller do
  describe 'GET #index' do
    subject { get :index }

    it { should have_http_status(:success) }              # 動く
    it { should permit(:title, :body).for(:post) }        # 動かない
  end
end
```

`subject`がレスポンスになってしまい、`permit`が期待するコントローラのインスタンスが渡らないためです。リクエストを先に実行したいなら`before`を使います。コントローラスペックでは`subject`を上書きしない、とルールで決めてしまうのが安全です。

```
RSpec.describe PostsController, type: :controller do
  describe 'GET #index' do
    before { get :index }

    it { expect(response).to have_http_status(:success) }
    it { should render_template('index') }
  end
end
```

### example groupごとに自動で読み込まれる範囲

Railsプロジェクトでは、マッチャーはexample groupの種類に応じて自動的に読み込まれます。ActiveRecord・ActiveModelのマッチャーは`type: :model`のグループかspec/models配下のファイル、ActionControllerのマッチャーは`type: :controller`のグループかspec/controllers配下、`route`マッチャーは`type: :routing`のグループかspec/routing配下でのみ有効です。`delegate_method`のような単独マッチャーだけは全グループで使えます。

モデルのマッチャーがリクエストスペックで使えないのは、この仕組みによるものです。

## 種類別マッチャー早見表（8.0.1時点）

8.0.1のREADMEが列挙するマッチャーを、検証対象の宣言と対応づけて一覧にしました。前章までで扱わなかったものも含みます。

| 分類               | マッチャー                           | 検証する宣言                           |
| ---------------- | ------------------------------- | -------------------------------- |
| ActiveModel      | validate\_presence\_of          | validates presence               |
| ActiveModel      | validate\_length\_of            | validates length                 |
| ActiveModel      | validate\_numericality\_of      | validates numericality           |
| ActiveModel      | validate\_comparison\_of        | validates comparison             |
| ActiveModel      | validate\_inclusion\_of         | validates inclusion              |
| ActiveModel      | validate\_exclusion\_of         | validates exclusion              |
| ActiveModel      | validate\_confirmation\_of      | validates confirmation           |
| ActiveModel      | validate\_acceptance\_of        | validates acceptance             |
| ActiveModel      | validate\_absence\_of           | validates absence                |
| ActiveModel      | allow\_value                    | 値ごとの妥当性                          |
| ActiveModel      | have\_secure\_password          | has\_secure\_password            |
| ActiveRecord     | validate\_uniqueness\_of        | validates uniqueness             |
| ActiveRecord     | belong\_to                      | belongs\_to                      |
| ActiveRecord     | have\_many                      | has\_many                        |
| ActiveRecord     | have\_one                       | has\_one                         |
| ActiveRecord     | have\_and\_belong\_to\_many     | has\_and\_belongs\_to\_many      |
| ActiveRecord     | have\_delegated\_type           | delegated\_type                  |
| ActiveRecord     | accept\_nested\_attributes\_for | accepts\_nested\_attributes\_for |
| ActiveRecord     | define\_enum\_for               | enum                             |
| ActiveRecord     | serialize                       | serialize                        |
| ActiveRecord     | normalize                       | normalizes                       |
| ActiveRecord     | encrypt                         | encrypts                         |
| ActiveRecord     | have\_rich\_text                | has\_rich\_text                  |
| ActiveRecord     | have\_one\_attached             | has\_one\_attached               |
| ActiveRecord     | have\_many\_attached            | has\_many\_attached              |
| ActiveRecord     | have\_db\_column                | テーブルの列                           |
| ActiveRecord     | have\_db\_index                 | 列のインデックス                         |
| ActiveRecord     | have\_readonly\_attribute       | attr\_readonly                   |
| ActiveRecord     | have\_implicit\_order\_column   | implicit\_order\_column          |
| ActionController | permit                          | params の許可属性                     |
| ActionController | filter\_param                   | ログ出力の除外                          |
| ActionController | redirect\_to                    | リダイレクト先                          |
| ActionController | render\_template                | 描画テンプレート                         |
| ActionController | render\_with\_layout            | レイアウト                            |
| ActionController | rescue\_from                    | rescue\_from                     |
| ActionController | respond\_with                   | ステータスコード                         |
| ActionController | set\_session                    | session の中身                      |
| ActionController | set\_flash                      | flash の中身                        |
| ActionController | use\_before\_action             | before\_action                   |
| ActionController | use\_after\_action              | after\_action                    |
| ActionController | use\_around\_action             | around\_action                   |
| ルーティング           | route                           | ルーティング定義                         |
| 単独               | delegate\_method                | delegate                         |

Rails 7.1で追加された`normalizes`に対応する`normalize`マッチャーは、入力と期待値をチェーンで書きます。`should normalize(:email).from(" ME@XYZ.COM").to("me@xyz.com")`のように、正規化の前後を1行で表現できる形です。ActiveRecord Encryptionを使っているなら`encrypt`で`encrypts`宣言の有無も検証できます。

## shoulda-matchersを使うべきでない場面

このgemを業務ロジックのテストに広げるのは誤用です。公式READMEの「A Note on Testing Style」が、shoulda-matchersはリフレクションとイントロスペクションで実装されている、つまり実装の内部構造を見に行くツールだと明言しています。振る舞いではなく宣言を検証しているため、テストが実装の形に強く結びつきます。

条件分岐を含むカスタムバリデーションには使えません。`validate :custom_check`のようにメソッドで書いた検証ロジックは、どのマッチャーでも表現できないからです。入力と結果の組み合わせを列挙する通常のテストを書きます。

計算処理や状態遷移などのドメインロジックも対象外です。「宣言があること」を確認しても、その処理が正しい答えを返すかは一切保証されません。

モデルのすべての属性に機械的にマッチャーを並べる運用も避けるべきです。`validates`の行をテストに書き写しているだけの状態になり、リファクタリングのたびに両方を直す二重メンテナンスが発生します。

逆に、このgemが最も価値を出すのは、テストが手薄になりがちなフレームワークの設定部分です。アソシエーションの`dependent: :destroy`が消えていないか、Strong Parametersの許可属性が意図せず増えていないか。この種の設定漏れは通常のテストでは見落としやすく、発生したときの影響が大きい領域です。ここに絞って使うのが正しい距離感になります。

## よくある質問

### gem ‘shoulda’ と gem ‘shoulda-matchers’ は何が違いますか？

shouldaは、shoulda-matchers（マッチャー本体）とshoulda-context（`context`や`should`といったMinitest向けの記法）をまとめたumbrella gemです。ただし2026年8月時点のshouldaは4.0.0（2020年6月13日）が最新で、依存が`shoulda-matchers ~> 4.0`に固定されています。8.0系の機能を使いたい場合は、shoulda経由ではなくshoulda-matchersを直接指定してください。

### Rails 7.1のプロジェクトでは使えませんか？

8.0系は使えません。8.0.0のリリースノートが後方互換性のない変更としてRails 7.1とRuby 3.2のサポート終了を明記しています。Rails 7.1環境では`gem 'shoulda-matchers', '~> 7.0'`と指定して7.0.1を使ってください。7.0系はRails 8・8.1にも対応済みなので、Rails側を上げるまでのつなぎとして問題なく機能します。

### マッチャーがNoMethodErrorになるのはなぜですか？

原因は主に2つです。1つはrails\_helper.rbの`Shoulda::Matchers.configure`ブロックの書き忘れで、gemを入れただけではマッチャーは有効になりません。もう1つはファイルの置き場所です。モデル系マッチャーはspec/models配下か`type: :model`を付けたグループでしか読み込まれないため、spec/servicesなどに置いたファイルでは未定義になります。`type:`タグを明示すれば解決します。

### should と is\_expected.to はどちらを使うべきですか？

どちらでも動作は同じです。公式ドキュメントは短さを理由に`should`を採用していますが、`it { is_expected.to validate_presence_of(:name) }`という書き方も正式にサポートされています。プロジェクトでRuboCopのRSpec系ルールを有効にしている場合は、警告が出ないほうに寄せてください。混在させると読み手が両方の記法を追うことになります。

### 宣言の検証以外は、どうテストを書けばいいですか？

入力と期待する結果の組み合わせを直接書く、通常のRSpecのテストで対応します。たとえば`validate :end_date_after_start_date`のようなカスタムバリデーションなら、開始日より前の終了日を持つレコードを組み立てて`expect(record).not_to be_valid`と`expect(record.errors[:end_date]).to be_present`を確認します。宣言の有無はshoulda-matchers、処理の結果は通常のテスト、という分担です。

## 関連記事

- [RSpecとは何か？テスト駆動開発におけるRSpecの役割と概要](/tech/details/3993/)
- [リクエストスペックとは？基本概念と目的についての説明](/tech/details/3993/)
- [RailsのAssociationとは？](/tech/details/2883/)
- [counter\_cacheとは？Railsでの実装手順とカウントずれ・後付けの対処](/tech/details/3727/)
- [RSpecでCapybaraとPlaywrightを導入するための基本手順](/tech/details/6355/)

---

出典: [shoulda-matchersの使い方【8.0対応】｜設定・主要マッチャー・バージョン選定](<https://www.issoh.co.jp/tech/details/3359/>)（株式会社一創）
