I'm trying to write a test to confirm that my application sends a 404 Not Found
in response to a request for an invalid record (say a record with id=0
).
My test code looks like this:
get '/pluralized_name_for_my_resource/0', as: :json
assert_response :not_found, "Doesn't return 404 Not Found for invalid id"
If I plug the corresponding URL into a browser, e.g. http://localhost:3000/pluralized_name_for_my_resource/0.json
, the app raises the activerecord exception internally (and prints a message to the logs about it) but correctly responds with a 404 Not Found
.
When I run the test, however, the exception isn't handled the same way. The test aborts and returns in the first get '/pluralized_...
line and is reported as an error rather than a passing test.
How can I make my test environment handle the exception in the same way as my development environment, so that my test passes?
I did a bit of experimenting and given rails g scaffold foo
and this test:
require "test_helper"
class FoosControllerTest < ActionDispatch::IntegrationTest
setup do
@foo = foos(:one)
end
test "should respond with not found" do
get foo_url('xxxxxx'), as: :json
assert_response :not_found
assert_equal({ "status" => 404, "error"=>"Not Found"}, response.parsed_body)
end
end
With the default settings the ActiveRecord::RecordNotFound exception is indeed not caught by the exception handler and bubbles up to the test.
Error:
FoosControllerTest#test_should_respond_with_not_found:
ActiveRecord::RecordNotFound: Couldn't find Foo with 'id'=xxxxxx
app/controllers/foos_controller.rb:63:in `set_foo'
test/controllers/foos_controller_test.rb:9:in `block in <class:FoosControllerTest>'
What seems to work is changing config.consider_all_requests_local
and config.action_dispatch.show_exceptions
in config/environments/test.rb
:
# Show full error reports and disable caching.
config.consider_all_requests_local = false # was true
config.action_controller.perform_caching = false
config.cache_store = :null_store
# Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = true # was false
With those two changes the test passes:
Running 1 tests in a single process (parallelization threshold is 50)
Run options: --seed 27622
# Running:
.
Finished in 0.270401s, 3.6982 runs/s, 7.3964 assertions/s.
1 runs, 2 assertions, 0 failures, 0 errors, 0 skips
However doing so might not be the best idea since its often useful that an exception in the application causes your tests to bail early. A hacky workaround is to use assert_raise
to catch the exception when it bubbles up to the test:
require "test_helper"
class FoosControllerTest < ActionDispatch::IntegrationTest
setup do
@foo = foos(:one)
end
test "should respond with not found" do
assert_raise(ActiveRecord::RecordNotFound) do
get foo_url('xxxxxx'), as: :json
assert_response :not_found
assert_equal({ "status" => 404, "error"=>"Not Found"}, response.parsed_body)
end
end
end