在某些RSpec rails请求示例中测试HTTP状态代码,但是对于其他RSPro rails请求示例

在使用rspec-rails测试的Rails 4.2.0应用程序中,我提供了一个带有类似REST资源的JSON Web API,其强制属性为mand_attr

我想测试一下,当POST请求中缺少该属性时,此API会回答HTTP代码400( BAD REQUEST )。 (请参阅第二个示例。)我的控制器尝试通过抛出ActionController::ParameterMissing来生成此HTTP代码,如下面的第一个RSpec示例所示。

其他 RSpec示例中,我希望通过示例(如果它们是预期的)挽救引发的exception或者命中测试运行器,因此它们会显示给开发人员(如果错误是意外的),因此我不想要去除

  # Raise exceptions instead of rendering exception templates. config.action_dispatch.show_exceptions = false 

来自config/environments/test.rb

我的计划是在请求规范中包含以下内容:

 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::Baseinheritance而是从ActionController::APIinheritance而且我不知道ActionController::API是否默认使用ActionDispatch::ExceptionWrapper ,或者我是否必须告诉我的控制器以某种方式这样做。

你想要使用RSpecfilter 。 如果以这种方式执行此操作,对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中的“任意元数据”,我在这里选择了命名以便于阅读。

从部分模拟的应用程序配置中返回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()缓存其结果。

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 

会做的伎俩。 请注意,我们从env_config() clone结果,以免我们修改会影响所有示例的原始Hash。

  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,所以我们可以直接修改它。

在我看来,exception测试不属于请求规范; 请求规范通常是从客户端的角度测试您的api,以确保您的整个应用程序堆栈按预期工作。 在测试用户界面时,它们在性质上也与特征测试类似。 因此,因为您的客户不会看到此exception,它可能不属于那里。

我也同情你对正确使用框架的关注,并希望确保这一点,但看起来你似乎过分参与了框架的内部工作。

我要做的是首先弄清楚我是否正确使用框架中的function(这可以通过TDD方法或作为尖峰来完成); 一旦我理解了如何完成我想要完成的任务,我就会编写一个请求规范,其中我扮演客户角色,而不用担心框架细节; 只测试给定特定输入的输出。

我有兴趣看到你在控制器中编写的代码,因为这也可以用来确定测试策略。 如果您编写了引发exception的代码,那么这可能是对其进行测试的理由,但理想情况下,这将是控制器的unit testing。 这将是rspec-rails环境中的控制器测试。