Rails の exceptions_app によるエラーページの表示をテストする

# 背景

`exceptions_app`の仕組みで例外を捕捉しようとしているとき(*)、その処理は Rack Middleware の`ActionDispatch::ShowExceptions`で行われます。本番環境 (`RAILS_ENV = production`) ではここで例外が捕捉されることによりエラーページが表示されます。
一方でテスト環境 (`RAILS_ENV = test`) では、デフォルトの設定のままではこれが無効になっており、エラーページは表示されません。
例外が発生することをテストすることができるため通常はこの動作は望ましいのですが、Requests Spec や Features Spec などで、エラー発生時の表示やステータスコードをテストしたい時があります。
そういう時のために、一時的に`ActionDispatch::ShowExceptions`での例外捕捉を有効にし、エラーページをレスポンスできるようにする方法を調べました。

*) `exceptions_app`については別記事「[Railsアプリの例外ハンドリングとエラーページの表示についてまとめてみた - Qiita](http://qiita.com/upinetree/items/273ae574f1c021d24c37)」を参照してください

# 前提

- Rails 4.2.6

# 手っ取り早いコード

実行exampleの前後に下記のような処理を行えばOKです。

```ruby
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](https://github.com/rails/rails/blob/4-2-stable/actionpack/lib/action_dispatch/railtie.rb#L8)です。

### 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`) では、下記のようになっています。

```ruby
config.consider_all_requests_local     = true
config.action_dispatch.show_exceptions = false
```

だから例外が発生してもエラーページが表示されなかったんですね。

また、テスト実行中の設定の切り替えは、`Rails.application.config`ではなく`Rails.application.env_config`でないと反映されません。
従って、example 実行前に

```ruby
Rails.application.env_config['action_dispatch.show_detailed_exceptions'] = false
Rails.application.env_config['action_dispatch.show_exceptions']          = true
```

このような設定をしていたというわけです。

# 参考

- [Configuring Rails Applications — Ruby on Rails Guides](http://guides.rubyonrails.org/configuring.html)
- [activate error handling in integration tests `with_rails_error_handling` by senny · Pull Request #11289 · rails/rails](https://github.com/rails/rails/pull/11289#issuecomment-118612393)