Rails: コントローラのconcernsをテストする方法(翻訳)
原注
本記事は私の著書『The Rails and Hotwire Codex』から抜粋・整形したものです。
Finally ... the damn thing is out in the wild.
It was quite easy in the end, only cost my about 99% of my sanity.#ruby #rails
Buy now 👇 https://t.co/sdL7GoBZpn.
— Ayush (@ayushn21) December 15, 2022
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/フォルダの下にあるファイルはオートロードされません。そのため、これらのファイルについては手動でrequire
とinclude
が必要になります。
# 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』もぜひチェックしてみてください。
概要
元サイトの許諾を得て翻訳・公開いたします。