Handling exceptions in Rails' test environment like in production
The other day I wanted to verify that a certain controller action returns a 404 response when a record isn't found. This is the default behavior for Rails applications - the framework will respond with the 404 code when it encounters an
ActiveRecord::RecordNotFound exception (read more below why this is happening). You do not need to raise that exception manually - the
.find method will raise the exception when a record doesn't exist, and other finder methods with a bang (e.g.
.find_by!) will behave in the same way.
What I didn't know is that the
test environment behaves differently than
production. Consider the following controller and corresponding request spec:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
# app/controllers/books_controller.rb class BooksController < ApplicationController def show @book = Book.find(params[:id]) end end # spec/requests/books_spec.rb require 'rails_helper' describe 'Books' do it 'returns a 404 when a book cannot be found' do get '/books/non-existing' expect(response).to have_http_status(404) end end
I would have expected the spec to pass with flying colors, but instead it failed with an
ActiveRecord::RecordNotFound: Couldn't find Book with 'id'=not-existing exception.
What's wrong here? Rails relies on the
ActionDispatch::ShowExceptions Rack middleware to rescue the
ActiveRecord::RecordNotFound exception and return the error response with the 404 HTTP status code. The middleware checks the
config.action_dispatch.show_exceptions setting, and if it's true, it will ask the ExceptionWrapper what status code it should return. If the setting is set to
false, however, it will just re-raise the exception. So, to make it work, change the following line in your
1 2 3 4 5 6
# config/environments/test.rb Rails.application.configure do ... config.action_dispatch.show_exceptions = true ... end
Re-run the spec and our request spec will now pass as originally intended. Of course this will not only work for
ActiveRecord::RecordNotFound exceptions, but for all exceptions that Rails handles by default. For a complete list, have a look at the
ExceptionWrapper class and the ActiveRecord railtie.