背景
exceptions_appの仕組みで例外を捕捉しようとしているとき(*)、その処理は Rack Middleware のActionDispatch::ShowExceptionsで行われます。本番環境 (RAILS_ENV = production) ではここで例外が捕捉されることによりエラーページが表示されます。
一方でテスト環境 (RAILS_ENV = test) では、デフォルトの設定のままではこれが無効になっており、エラーページは表示されません。
例外が発生することをテストすることができるため通常はこの動作は望ましいのですが、Requests Spec や Features Spec などで、エラー発生時の表示やステータスコードをテストしたい時があります。
そういう時のために、一時的にActionDispatch::ShowExceptionsでの例外捕捉を有効にし、エラーページをレスポンスできるようにする方法を調べました。
*) exceptions_appについては別記事「Railsアプリの例外ハンドリングとエラーページの表示についてまとめてみた - Qiita」を参照してください
前提
- Rails 4.2.6
手っ取り早いコード
実行exampleの前後に下記のような処理を行えばOKです。
shared_context 'Show Exceptions', show_exceptions: true do
around(:each) do |example|
show_detailed_exceptions = Rails.application.env_config['action_dispatch.show_detailed_exceptions']
show_exceptions = Rails.application.env_config['action_dispatch.show_exceptions']
Rails.application.env_config['action_dispatch.show_detailed_exceptions'] = false
Rails.application.env_config['action_dispatch.show_exceptions'] = true
example.run
Rails.application.env_config['action_dispatch.show_detailed_exceptions'] = show_detailed_exceptions
Rails.application.env_config['action_dispatch.show_exceptions'] = show_exceptions
end
end
# `show_exceptions`メタを付けて実行する
describe 'Get /hoge', show_exceptions: true do
...
end
どうしてそうなるか、ざっくり説明していきます。
その説明の前提として、まずは Rack Middleware について少し説明します(知ってる人は飛ばしちゃってください)
Rack Middleware について
Railsデフォルトの Rack Middleware はRails::Application::DefaultMiddlewareStackで定義されており、下記のrakeタスクでも確認できます。
$ bin/rake middleware
... (省略) ...
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
... (省略) ...
run MyApp::Application.routes
この定義によると、ActionDispatch::ShowExceptionsが外側に、ActionDispatch::DebugExceptionsが内側にスタックされています。
前者はexceptions_appによる例外捕捉の仕組みで、後者はデバッグ画面や例外のログ出力などのデバッグを目的とした仕組みです。
つまり内側のDebugExceptionsで捕捉されなかった例外が、外側のShowExceptionsで捕捉されます.
Rails Application から Rack Middleware が call される際、ハッシュenvが渡されます。
このenvにはRails::Application#env_configがあらかじめ結合されて、env_configにはRails.application.configで設定した値が割り当てられます。
(Rails.application.configは、config/application.rbやconfig/environments/development.rbなどで設定するやつです)
env_configは Rack Middleware 起動時にセットされます。それ以降はRails.application.configを設定しなおしてもenv_configには反映されないので、テスト実行中に切り替えるにはenv_configを直接設定し直す必要があります。
これらの前提をもとに、冒頭のコードに出てきたenv_config['action_dispatch.show_exceptions']とenv_config['action_dispatch.show_detailed_exceptions']について説明します。
エラーページ表示条件に関わるconfigたち
env_config['action_dispatch.show_exceptions']
ActionDispatch::ShowExceptionsとActionDispatch::DebugExceptionsが例外を捕捉するかどうかを決める値です。
元になっている設定はconfig.action_dispatch.show_exceptionsで、これはデフォルトtrueです。
true のとき
-
action_dispatch.show_detailed_exceptionsも true のとき、DebugExceptionsで例外を捕捉してデバッグ用エラーページを表示 -
action_dispatch.show_detailed_exceptionsが false なら、DebugExceptionsでは例外を捕捉せず、外側のShowExceptionsで捕捉しエラーページを表示
false のとき
-
DebugExceptionsでもShowExceptionsでも例外を捕捉しない
env_config['action_dispatch.show_detailed_exceptions']
ActionDispatch::DebugExceptionsがデバッグ用エラーページを表示するかどうかを決める値です。
元になっている設定はconfig.consider_all_requests_localで、名前が異なるのと、他の用途でも使われるので注意ですね。
action_dispatch.show_exceptionsが true のときに有効になりますので、以下はそれが前提の説明です。
true のとき
-
DebugExceptionsで捕捉した例外をもとに、デバッグ用エラーページを表示
false のとき
- そのまま例外を raise する。その例外はその後
ShowExceptionsで捕捉され、エラーページが表示
テスト環境では
rails generateで生成されるデフォルトの設定 (config/environments/test.rb) では、下記のようになっています。
config.consider_all_requests_local = true
config.action_dispatch.show_exceptions = false
だから例外が発生してもエラーページが表示されなかったんですね。
また、テスト実行中の設定の切り替えは、Rails.application.configではなくRails.application.env_configでないと反映されません。
従って、example 実行前に
Rails.application.env_config['action_dispatch.show_detailed_exceptions'] = false
Rails.application.env_config['action_dispatch.show_exceptions'] = true
このような設定をしていたというわけです。