Rails: Testing exceptions with the rescue_responses setting

In Rails 7.2 Show archive.org snapshot the new default for config.action_dispatch.show_exceptions is rescuable.

  • :rescuable: It will show a Rails error page in the response only for rescuable exceptions as
    defined by ActionDispatch::ExceptionWrapper.rescue_responses. In the
    event of an unexpected internal server error, the exception that caused
    the error will still be raised within the test so as to provide a useful
    stack trace and a good debugging experience.
  • :all: It will show a Rails error page in the response for all exceptions (previously true)
  • :none: It will raise for all exceptions (previous default behavior, value false)

Using the new default needs to change a RSpec test like this:

Before

it 'raises an exception if the page could not be found' do
  expect { get '/example/' }
    .to raise_exception ActiveRecord::RecordNotFound
end

After

it 'raises an exception if the page could not be found' do
  get '/example/'
  expect(response).to have_http_status(:not_found)
end

Edge cases

In case you really need to check the underlying exception, you can temporary change the show_exception setting:

RSpec.configure do |config|
  config.around(:each, :show_exceptions) do |example|
    show_exceptions = Rails.application.env_config['action_dispatch.show_exceptions']
    Rails.application.env_config['action_dispatch.show_exceptions'] = example.metadata[:show_exceptions]

    example.run
  ensure
    Rails.application.env_config['action_dispatch.show_exceptions'] = show_exceptions
  end
end
it 'raises an exception if the page could not be found', show_exceptions: :none do
  expect { get '/example/' }
    .to raise_exception ActiveRecord::RecordNotFound
end

Additional notes regarding the implementation:

  • get '/example/', headers: { 'action_dispatch.show_exceptions' => :none } is valid, but the middleware doesn't allow to set the header from outside.
  • allow(Rails.application.config.action_dispatch).to receive(:show_exceptions).and_return(:none) is valid, but there is a underlying Rails.application.env_config object that caches the value between test. Resulting in flaky tests.
Emanuel