RSpecテスト共通化の鍵!shared_examplesとshared_context 完全ガイド

RSpecでテストを書いていると、「あれ、このテストコード、他の場所でも書いたな…」と感じることがありませんか?特に、似たような振る舞いをするクラスや、同じようなセットアップが必要なテストが増えてくると、コードの重複は避けられない問題になりがちです。

重複したコードは、修正が必要になった際に複数の箇所を変更する必要があり、バグの温床になったり、メンテナンスコストを増大させたりします。この問題を解決し、テストコードをDRY (Don’t Repeat Yourself) に保つための強力な武器が、RSpecに用意されているshared_examplesshared_contextです。

この記事では、RSpecのテストコード共通化に不可欠なshared_examplesshared_contextについて、基本的な使い方から違い、実践的な使い分け、注意点まで、サンプルコードを交えながら「完全ガイド」として徹底解説します。

この記事を読むことで、あなたは以下のことができるようになります。

  • shared_examplesshared_contextの基本的な使い方を理解する。
  • 両者の明確な違いと、適切な使い分け方を判断できるようになる。
  • テストコードの重複を減らし、より保守性の高いコードを書くためのベストプラクティスを知る。

RSpecを使い始めたばかりの方から、テストコードのリファクタリングを考えている方まで、ぜひ参考にしてください。

目次

shared_examplesとshared_contextとは?

まず、それぞれの基本的な役割を理解しましょう。

  • shared_examples: 複数のテストグループ(describecontextブロック)で**共通の振る舞い(一連のitテスト例)**を共有するために使います。「異なる対象が同じように動作すること」をテストするのに適しています。
  • shared_context: 複数のテストグループで**共通のセットアップ(beforeフック、let変数、ヘルパーメソッドなど)**を共有するために使います。「同じ前提条件の下で異なるテスト」を実行するのに適しています。

この違いが、両者を使い分ける上での最も重要なポイントです。

shared_examples: テストの「振る舞い」を共有する

同じようなテストケースが複数の場所に現れる場合にshared_examplesが役立ちます。

基本的な書き方と使い方

shared_examplesは以下のように定義します。

Ruby

# spec/support/shared_examples/some_behavior.rb (推奨される配置場所)

RSpec.shared_examples "何らかの共通の振る舞い" do
  # ここに共通の `it` ブロックを記述する
  # let や subject は呼び出し側で定義されることを期待する

  it '○○であること' do
    # expect(subject)....
  end

  it '△△を返すこと' do
    # expect(subject.some_method)....
  end
end

そして、この共通の振る舞いをテストしたい場所でit_behaves_likeを使って呼び出します。

Ruby

# spec/models/class_a_spec.rb
require 'rails_helper'
# require 'support/shared_examples/some_behavior' # 個別にrequireする場合

RSpec.describe ClassA do
  subject { described_class.new(...) }
  # 必要に応じて let で変数を定義

  it_behaves_like "何らかの共通の振る舞い"
end

# spec/models/class_b_spec.rb
require 'rails_helper'
# require 'support/shared_examples/some_behavior'

RSpec.describe ClassB do
  subject { described_class.new(...) }
  # 必要に応じて let で変数を定義

  it_behaves_like "何らかの共通の振る舞い"
end

it_behaves_likeの代わりにinclude_examplesを使うこともできますが、it_behaves_likeは独立したcontextとして展開されるため、テスト結果の出力が見やすいことが多いです。

(補足) shared_examples_forというエイリアス(別名)もありますが、現在はshared_examplesの使用が一般的です。

引数を渡す

shared_examplesに引数を渡すことで、テストの振る舞いをより柔軟にカスタマイズできます。

Ruby

# spec/support/shared_examples/status_check.rb
RSpec.shared_examples "特定のステータスを持つ" do |expected_status|
  it "ステータスが #{expected_status} であること" do
    expect(subject.status).to eq(expected_status)
  end
end

# spec/models/order_spec.rb
RSpec.describe Order do
  context '作成直後の場合' do
    subject { Order.create(...) }
    it_behaves_like "特定のステータスを持つ", "pending"
  end

  context '発送済みの場合' do
    subject { Order.create(..., shipped_at: Time.current) }
    it_behaves_like "特定のステータスを持つ", "shipped"
  end
end

letsubjectとの連携

shared_examples内で使われるlet変数やsubjectは、呼び出し元のdescribecontextブロックで定義されたものが使われます。これにより、異なる対象(subject)に対して同じテスト(shared_examples)を適用できます。

shared_context: テストの「セットアップ」を共有する

テストを実行するための前提条件(データの準備、ログイン状態、ヘルパーメソッドなど)が複数のテストで共通している場合にshared_contextが役立ちます。

基本的な書き方と使い方

shared_contextは以下のように定義します。

Ruby

# spec/support/shared_contexts/login_user.rb (推奨される配置場所)

RSpec.shared_context "ログイン済みのユーザー" do
  let(:current_user) { FactoryBot.create(:user) } # 例: FactoryBotを使用

  before do
    # ここにログイン処理のシミュレーションなどを記述
    sign_in(current_user) # Devise のヘルパーメソッド sign_in を使う例
  end

  def common_helper_method
    # テストで使える共通のヘルパーメソッド
    "shared helper result"
  end
end

そして、この共通のセットアップを使いたい場所でinclude_contextを使って呼び出します。

Ruby

# spec/controllers/posts_controller_spec.rb
require 'rails_helper'
# require 'support/shared_contexts/login_user' # 個別にrequireする場合

RSpec.describe PostsController, type: :controller do

  describe 'ログインが必要なアクション' do
    # 共通のセットアップを読み込む
    include_context "ログイン済みのユーザー"

    it 'GET #index は成功すること' do
      get :index
      expect(response).to be_successful
      # ここでは current_user が利用可能
      # common_helper_method も利用可能
    end

    it 'POST #create は投稿を作成できること' do
      post :create, params: { post: { title: 'Test', body: '...' } }
      # ... アサーション ...
    end
  end

  describe 'ログインが不要なアクション' do
    # ここでは include_context を呼び出さない
    it 'GET #show は成功すること' do
      # ...
    end
  end
end

include_contextを呼び出したスコープ内では、shared_context内で定義されたlet変数、beforeフック、ヘルパーメソッドが利用可能になります。

shared_examples vs. shared_context: 違いと比較、使い分け

ここで、両者の違いを整理し、どちらを使うべきか判断する基準を明確にしましょう。

比較項目shared_examplesshared_context
主な目的振る舞い(テスト例)の共有セットアップ(前提条件)の共有
共有されるものit specify ブロック群let subject before ヘルパーメソッド等
呼び出し方法it_behaves_likeinclude_examplesinclude_context
ユースケース・異なる対象の同じ振る舞いをテスト
・インターフェースのテスト
・共通のデータ準備
・ログイン状態の再現
・共通ヘルパー

使い分けのヒント:

  • 「この一連のテスト(itブロック群)、他の場所でも全く同じように使いたいな」 -> shared_examples
  • 「このテストの準備(letbefore)、他のテストでも必要だな」 -> shared_context

これらは組み合わせて使うことも可能です。例えば、shared_contextで共通のセットアップを行い、そのコンテキスト内でshared_examplesを使って共通の振る舞いをテストする、といった使い方もできます。

ベストプラクティスと注意点

shared_examplesshared_contextは強力ですが、使い方を間違えると逆にテストを複雑にしてしまう可能性もあります。以下の点に注意しましょう。

1.ファイル構成:

  • 共有するコードは spec/support/shared_examplesspec/support/shared_contexts といった専用ディレクトリに置くのが一般的です。
  • これらのファイルは rails_helper.rb (または spec_helper.rb) で一括して require するか、必要なファイルで個別に require します。 Ruby
# spec/rails_helper.rb or spec/spec_helper.rb
# Dir[Rails.root.join('spec/support/**/*.rb')].sort.each { |f| require f } # 一括ロード例

2.明確な命名:

  • 共有ブロックには、その内容が明確にわかる名前( "..." の部分)を付けましょう。「何が」共有されているのかが一目でわかるようにすることが重要です。

3.スコープを小さく保つ:

  • 一つの共有ブロックにあれもこれも詰め込みすぎず、関心事を分離しましょう。肥大化した共有ブロックは理解や再利用が困難になります。

4.依存関係を意識する:

  • shared_examplesが特定のlet変数が定義されていることを暗黙的に期待している場合、その依存関係がわかりにくくなることがあります。必要に応じてドキュメントコメントを残したり、引数で明示的に渡したりすることを検討しましょう。

5.乱用しない:

  • 共通化は目的ではなく手段です。無理に共通化しようとして、かえってテストコードが複雑で読みにくくなっては本末転倒です。可読性とDRYのバランスを常に意識しましょう。

まとめ

RSpecのshared_examplesshared_contextは、テストコードの重複を減らし、保守性と可読性を向上させるための強力な機能です。

  • 振る舞いを共有するなら shared_examples
  • セットアップを共有するなら shared_context

この基本を理解し、今回紹介した使い方やベストプラクティスを参考に、あなたのテストコードをよりクリーンで効率的なものに改善していきましょう。

DRYなテストは、将来のコード変更に対する自信を与えてくれます。ぜひ、積極的にこれらの機能を活用してみてください。

より詳細な情報については、RSpecの公式ドキュメントなども参照することをお勧めします]。

未経験からエンジニアへ転職!おすすめの転職サービスはこちら

「未経験だけどエンジニアになりたい…」「IT業界に興味があるけど、どこから始めるべきかわからない…」
そんな方におすすめなのが、プログラミングスクールを活用した転職活動です。
実績豊富なスクールを利用すれば、未経験からでもエンジニアとしての転職がぐっと近づきます!

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次