Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: コントローラのconcernsをテストする方法(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

Rails: コントローラのconcernsをテストする方法(翻訳)

原注

本記事は私の著書『The Rails and Hotwire Codex』から抜粋・整形したものです。

concernsは、Railsのコードを整理して重複を解消するのに便利な方法です。私が好きなconcernsの使い道のひとつは、ApplicationControllerにあるものをすべてconcernsで抽象化することです。
しかしここで疑問が生じます。concernsはどうやってテストすればよいのでしょうか?

concernsは常にクラスとミックスイン(mixin)されるものであり、単独では使われないので、concernsのテスト方法は少々迷います。クラスの振る舞いを確認するテストを書くと、ついでにconcernもテストされれば理想かもしれません。

しかし、そのconcernが他の多くのクラスにも同じ機能を追加していたらどうすればよいのでしょうか?たとえば、すべてのWebリクエストをAuthenticate(認証)するconcernがあるとしましょう。この認証機能をすべてのコントローラでテストするのは現実的ではありません。このような場合は、concernsを分離してテストするのがベストだと思います。

🔗 コントローラconcerns用のテストハーネス

引き続き、Authenticateという名前のconcernを仮の例として説明します。このconcernはコントローラ内部でしか使わないので、独立したテストを行うためのテストハーネス(test harness)1が必要です。

test/ディレクトリの下にsupport/ディレクトリを作成し、そこにテストハーネスで使うファイルを置きます。

$ mkdir test/support
$ touch test/support/test_controller.rb
$ touch test/support/routes_helper.rb

このTestController自身は大したことをする必要はなく、個別のテストでサブクラス化されます。各アクションがコントローラ名とアクション名をレンダリングして、テストケースでアサーションされるようにします。

class TestController < ActionController::Base
  def index; end
  def new; end
  def create; end
  def show; end
  def edit; end
  def update; end
  def destroy; end

  private

    def default_render
      render plain: "#{params[:controller]}##{params[:action]}"
    end
end

次に、テストケースでTestControllerサブクラスを指すテスト専用ルーティングを生成する方法が必要です。このテスト用ルーティングは/testのみを対象とするので、既存のルーティングと衝突しません。

# test/support/routes_helper.rb

# テストケースで生成されたルーティングをすべて消去するために、
# テストの`teardown`で`reload_routes!`が呼び出されるようにすること

module RoutesHelpers
  def draw_test_routes(&block)
    # 呼び出しではルーティングを削除しないこと
    Rails.application.routes.disable_clear_and_finalize = true

    Rails.application.routes.draw do
      scope "test" do
        instance_exec(&block)
      end
    end
  end

  def reload_routes!
    Rails.application.reload_routes!
  end
end

このdraw_test_routesヘルパーはブロックを受け取り、そのブロックはRails.application.routes.drawのコンテキスト内部で実行されます。要するにconfig/routes.rbでやっていることと同じですが、テストスイートのコンテキストで行う点が異なります。

Railsアプリのtest/フォルダの下にあるファイルはオートロードされません。そのため、これらのファイルについては手動でrequireincludeが必要になります。

# test/test_helper.rb

# ...

Dir[Rails.root.join("test", "support", "**", "*.rb")].each {
  |f| require f
}

# ...

class ActionDispatch::IntegrationTest
  include RoutesHelpers
end

テストハーネスの準備ができたら、いよいよAuthenticate concernのテストを書けるようになります。

$ touch test/controllers/concerns/authenticate_test.rb
require 'test_helper'

class AuthenticateTestsController < TestController
  include Authenticate

  def show
    # ...
  end
end

class AuthenticateTest < ActionDispatch::IntegrationTest
  setup do
    draw_test_routes do
      resource :authenticate_test, only: [:new, :create, :show, :edit]
    end
  end

  teardown do
    reload_routes!
  end

   # ...
   # ここにテストケースを書く
   # ...
end

テスト専用のAuthenticateTestsControllerが周辺機能を取り除いてくれるので、Authenticateにあるコードのテストに専念できるようになります。これで、普通のコントローラと同様にconcernsをテストできるようになりました。


本記事を気に入っていただけましたら、RailsとHotwireのスキルをレベルアップする『The Rails and Hotwire Codex』もぜひチェックしてみてください。

関連記事

Rails API: ActiveSupport::ConcernとModule::Concerning(翻訳)

Rails: 私の好きなコード(3)”正しく書かれた” concerns(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。