Тестирование кода статуса HTTP в некоторых RSpec-рельсах запрашивает экзамены, но для повышенного исключения в других
В приложении Rails 4.2.0, протестированном с помощью rspec-rails
, я предоставляю веб-API JSON ресурсом, подобным REST, с обязательным атрибутом mand_attr
.
Я бы хотел проверить, что этот API отвечает с кодом HTTP 400 (BAD REQUEST
), когда этот атрибут отсутствует в запросе POST. (см. второй пример). Мой контроллер пытается вызывают этот HTTP-код, бросая ActionController::ParameterMissing
, как показано первым примером RSpec ниже.
В других примерах RSpec, я хочу, чтобы исключения были спасены примерами (если они ожидаются) или попали в тестовый бегун, поэтому они отображаются разработчику (если ошибка неожиданна), поэтому я не хочу удалять
# Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = false
из config/environments/test.rb
.
Мой план состоял в следующем: 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
Я нашел два рабочих и один частично работающих решения, которые я выложу в качестве ответов. Но я не особенно доволен ни одним из них, поэтому, если вы можете придумать что-то лучшее (или просто другое), , я бы хотел увидеть ваш подход! Кроме того, если спецификация запроса неправильный тип спецификации, чтобы проверить это, я хотел бы знать это.
Я предвижу вопрос
Почему вы проверяете структуру Rails вместо своего приложения Rails? Рамка Rails имеет собственные тесты!
поэтому позвольте мне ответить на это упреждающе: я чувствую, что я не тестирую сам фрейм, но правильно ли я использую фреймворк. Мой контроллер не наследует от ActionController::Base
, а от ActionController::API
, и я не знал, использует ли ActionController::API
ActionDispatch::ExceptionWrapper
по умолчанию, или мне сначала пришлось бы сказать моему контроллеру сделать это как-то.
Ответы
Ответ 1
Вы хотите использовать RSpec фильтры. Если вы сделаете это так, изменение к Rails.application.config.action_dispatch.show_exceptions
будет локальным для примера и не будет мешать вашим другим тестам:
# This configure block can be moved into a spec helper
RSpec.configure do |config|
config.before(:example, exceptions: :catch) do
allow(Rails.application.config.action_dispatch).to receive(:show_exceptions) { true }
end
end
RSpec.describe 'POST' do
let(:perform_request) { post '/my/api/my_ressource', request_body }
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
end
context 'in production', exceptions: :catch do
it 'reports BAD REQUEST (HTTP status 400)' do
perform_request
expect(response).to be_a_bad_request
end
end
end
end
exceptions: :catch
- это "произвольные метаданные" в RSpec, я выбрал здесь название для удобочитаемости.
Ответ 2
Возврат nil
из частично изделенной конфигурации приложения с помощью
context 'in production' do
before do
allow(Rails.application.config.action_dispatch).to receive(:show_exceptions)
end
it 'reports BAD REQUEST (HTTP status 400)' do
perform_request
expect(response).to be_a_bad_request
end
end
или более явно с помощью
context 'in production' do
before do
allow(Rails.application.config.action_dispatch).to receive(:show_exceptions).and_return nil
end
it 'reports BAD REQUEST (HTTP status 400)' do
perform_request
expect(response).to be_a_bad_request
end
end
будет работать, если это был единственный пример запуска. Но если бы это было, мы могли бы точно так же падение настройки config/environments/test.rb
, так что это немного спорно. Если есть несколько примеров, это не будет работать, так как Rails.application.env_config()
, который запрашивает этот параметр, кэширует его результат.
Ответ 3
context 'in production' do
around do |example|
# Run examples without the setting:
show_exceptions = Rails.application.env_config.delete 'action_dispatch.show_exceptions'
example.run
# Restore the setting:
Rails.application.env_config['action_dispatch.show_exceptions'] = show_exceptions
end
it 'reports BAD REQUEST (HTTP status 400)' do
perform_request
expect(response).to be_a_bad_request
end
end
сделает трюк, но чувствует себя немного грязным. Он работает, потому что Rails.application.env_config()
предоставляет доступ к базовому Hash, который он использует для кэширования его результата, поэтому мы можем напрямую его изменить.
Ответ 4
Отказывание Rails.application.env_config()
для возврата измененного результата
context 'in production' do
before do
# We don't really want to test in a production environment,
# just in a slightly deviating test environment,
# so use the current test environment as a starting point ...
pseudo_production_config = Rails.application.env_config.clone
# ... and just remove the one test-specific setting we don't want here:
pseudo_production_config.delete 'action_dispatch.show_exceptions'
# Then let `Rails.application.env_config()` return that modified Hash
# for subsequent calls within this RSpec context.
allow(Rails.application).to receive(:env_config).
and_return pseudo_production_config
end
it 'reports BAD REQUEST (HTTP status 400)' do
perform_request
expect(response).to be_a_bad_request
end
end
сделает трюк. Обратите внимание, что мы clone
результат из env_config()
, чтобы мы не модифицировали исходный хэш, который повлиял бы на все примеры.
Ответ 5
По моему мнению, тест исключения не входит в спецификацию запроса; спецификации запроса, как правило, проверяют ваш api с точки зрения клиента, чтобы убедиться, что весь стек приложения работает должным образом. Они также аналогичны по своей природе для тестирования характеристик при тестировании пользовательского интерфейса. Поэтому, поскольку ваши клиенты не будут видеть это исключение, оно, вероятно, не принадлежит.
Я также могу сочувствовать вашей заботе о правильном использовании структуры и желании убедиться в этом, но похоже, что вы слишком вовлечены в внутреннюю работу фреймворка.
Что я буду делать, сначала выясню, правильно ли я использую эту функцию в структуре (это можно сделать с помощью подхода TDD или как всплеск); как только я пойму, как выполнить то, что я хочу выполнить, я напишу спецификацию запроса, в которой я беру на себя роль клиента, и не беспокоиться о деталях структуры; просто проверьте выходные данные на конкретные входы.
Мне было бы интересно увидеть код, который вы написали в своем контроллере, потому что это также можно использовать для определения стратегии тестирования. Если вы написали код, который вызывает исключение, то это может оправдать его проверку, но в идеале это будет unit test для контроллера. Это будет тест контроллера в среде rspec-rails
.