20

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

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

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

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

4

5 回答 5

6

您需要为此使用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 中的“任意元数据”,为了便于阅读,我在这里选择了命名。

于 2016-02-17T08:04:13.600 回答
0
    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

will do the trick, but feels kinda dirty. It works because Rails.application.env_config() gives access to the underlying Hash it uses for caching its result, so we can directly modify that.

于 2015-04-04T11:24:56.657 回答
0

模拟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(),以免我们修改会影响所有示例的原始哈希。

于 2015-04-04T11:33:29.613 回答
0

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()查询此设置,缓存其结果。

于 2015-04-04T11:18:03.750 回答
0

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

我也可以同情您对正确使用框架并希望确保这一点的担忧,但看起来您似乎过于关注框架的内部运作。

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

我有兴趣查看您在控制器中编写的代码,因为这也可用于确定测试策略。如果您编写了引发异常的代码,那么这可能证明对其进行测试是合理的,但理想情况下,这将是控制器的单元测试。这将是rspec-rails环境中的控制器测试。

于 2015-12-05T06:10:39.300 回答