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 test.rb file:

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.