在某些 RSpec rails 请求示例中测试 HTTP 状态代码,但在其他示例中测试引发异常
Posted
技术标签:
【中文标题】在某些 RSpec rails 请求示例中测试 HTTP 状态代码,但在其他示例中测试引发异常【英文标题】:Test for HTTP status code in some RSpec rails request exampes, but for raised exception in others 【发布时间】:2015-06-09 08:16:03 【问题描述】:在使用rspec-rails
测试的Rails 4.2.0 应用程序中,我提供了一个JSON Web API,它带有一个带有强制属性mand_attr
的类似REST 的资源。
当 POST 请求中缺少该属性时,我想测试此 API 是否以 HTTP 代码 400 (BAD REQUEST
) 回答。(参见第二个示例。)我的控制器尝试通过抛出 ActionController::ParameterMissing
来触发这个 HTTP 代码,如下面的第一个 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】:从部分模拟的应用程序配置中返回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()
会查询此设置,并缓存其结果。
【讨论】:
【参考方案2】: 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()
可以访问它用于缓存其结果的底层哈希,因此我们可以直接对其进行修改。
【讨论】:
【参考方案3】:模拟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()
的结果,以免我们修改原来的Hash影响所有的例子。
【讨论】:
【参考方案4】:在我看来,异常测试不属于请求规范;请求规范通常是从客户端的角度测试您的 api,以确保您的整个应用程序堆栈按预期工作。它们在性质上也类似于测试用户界面时的功能测试。因此,由于您的客户不会看到此异常,因此它可能不属于那里。
我也可以理解您对正确使用框架并希望确保这一点的担忧,但您似乎过于关注框架的内部运作。
我要做的是首先弄清楚我是否正确使用了框架中的功能,(这可以通过 TDD 方法或作为尖峰来完成);一旦我了解了如何完成我想要完成的事情,我会编写一个请求规范,我扮演客户的角色,而不用担心框架细节;只需在给定特定输入的情况下测试输出。
我很想看看您在控制器中编写的代码,因为这也可以用来确定测试策略。如果您编写了引发异常的代码,那么这可能证明对其进行测试是合理的,但理想情况下,这将是控制器的单元测试。这将是 rspec-rails
环境中的控制器测试。
【讨论】:
编写一个检查正确异常的控制器规范示例很简单。但是,使用 Rails 的默认测试设置,request spec 示例将也看到异常而不是 HTTP 状态代码,而不是 生产客户端,这将看到后者。我认为该默认设置是经过认真考虑的,因此我希望仅针对该示例进行偏差测试设置,而不是针对我的所有请求规范。【参考方案5】:您可能希望为此使用 RSpec filters。如果您这样做,则对 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 中是“任意元数据”,为了便于阅读,我选择了这里的命名。
【讨论】:
不幸的是,这似乎仅在首先执行此示例时才有效。当其他示例(不带exceptions: :catch
标志)首先运行时,中间件堆栈是使用ShowExceptions
中间件构建的,看起来这个堆栈在示例之间共享。
TL;DR 这使得我们的测试依赖于顺序。以上是关于在某些 RSpec rails 请求示例中测试 HTTP 状态代码,但在其他示例中测试引发异常的主要内容,如果未能解决你的问题,请参考以下文章
如何在 rspec rails 请求测试中发布缺少authentity_token 的帖子?