In a Rails 4.2.0 application tested with rspec-rails
, I provide a JSON web API with a REST-like resource with a mandatory attribute mand_attr
.
I'd like to test that this API answers with HTTP code 400 (BAD REQUEST
) when that attribute is missing from a POST request. (See second example blow.) My controller tries to cause this HTTP code by throwing an ActionController::ParameterMissing
, as illustrated by the first RSpec example below.
In other RSpec examples, I want raised exceptions to be rescued by the examples (if they're expected) or to hit the test runner, so they're displayed to the developer (if the error is unexpected), thus I do not want to remove
# Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = false
from config/environments/test.rb
.
My plan was to have something like the following in a request spec:
describe 'POST' do
let(:perform_request) { post '/my/api/my_ressource', request_body, request_header }
let(:request_header) { { 'CONTENT_TYPE' => 'application/json' } }
context 'without mandatory attribute' do
let(:request_body) do
{}.to_json
end
it 'raises a ParameterMissing error' do
expect { perform_request }.to raise_error ActionController::ParameterMissing,
'param is missing or the value is empty: mand_attr'
end
context 'in production' do
###############################################################
# How do I make this work without breaking the example above? #
###############################################################
it 'reports BAD REQUEST (HTTP status 400)' do
perform_request
expect(response).to be_a_bad_request
# Above matcher provided by api-matchers. Expectation equivalent to
# expect(response.status).to eq 400
end
end
end
# Below are the examples for the happy path.
# They're not relevant to this question, but I thought
# I'd let you see them for context and illustration.
context 'with mandatory attribute' do
let(:request_body) do
{ mand_attr: 'something' }.to_json
end
it 'creates a ressource entry' do
expect { perform_request }.to change(MyRessource, :count).by 1
end
it 'reports that a ressource entry was created (HTTP status 201)' do
perform_request
expect(response).to create_resource
# Above matcher provided by api-matchers. Expectation equivalent to
# expect(response.status).to eq 201
end
end
end
I have found two working and one partially working solutions which I'll post as answers. But I'm not particularly happy with any of them, so if you can come up with something better (or just different), I'd like to see your approach! Also, if a request spec is the wrong type of spec to test this, I'd like to know so.
I foresee the question
Why are you testing the Rails framework instead of just your Rails application? The Rails framework has tests of its own!
so let me answer that pre-emptively: I feel I'm not testing the framework itself here, but whether I'm using the framework correctly. My controller doesn't inherit from ActionController::Base
but from ActionController::API
and I didn't know whether ActionController::API
uses ActionDispatch::ExceptionWrapper
by default or whether I first would have had to tell my controller to do so somehow.
exceptions: :catch
flag) are run first, the middleware stack is built with theShowExceptions
middleware, and it seems like this stack is shared between examples. – Dogmatic