在RSpec中,如何期望标题包含在可接受值列表中?

时间:2016-05-02 14:53:52

标签: ruby unit-testing rspec

我正在尝试编写一个规范,声明来自API调用的HTTP头都包含在可接受的头文件列表中(同样具有可接受的值)。

我最后写了这样的东西:

expect(response.headers).to all(be_included_in(acceptable_headers))

其中be_included_in是自定义匹配器:

RSpec::Matchers.define :be_included_in do |enumerable|
  match do |element|
    enumerable.include?(element)
  end
end

这适用于声明标题都在包含范围内,但不满足测试其值以供接受的要求。

任何想法如何优雅地做到这一点?

2 个答案:

答案 0 :(得分:2)

这是一个解决方案,它将您的初始尝试的风格与审核实际标题的方法相结合,以对象Hash of Header-Name => RSpec匹配器。它完成以下任务:

  • expect()调用中的响应中获取标题可以使匹配器保持简单,并且可以将其全部用于标题,因为每个人都知道HTTP,所以很容易思考。
  • 它不使用否定的匹配器,这使得它更容易思考而不是具有多个否定的解决方案。
  • 它处理了一些你的双阴性解决方案没有的情况,我将在下面描述。

这是匹配器:

# I changed the first acceptable header and added a second to test that
# the matcher handles multiple acceptable headers correctly
let(:acceptable_headers) do
  {
    'Content-Type' => match(/^[a-z\-_.]+\/[a-z\-_.]+$/),
    'Content-Length' => match(/^\d+$/)
  }
end

RSpec::Matchers.define :all_be_acceptable_headers do
  match do |actual|
    actual.all? do |actual_key, actual_value|
      acceptable_headers.any? do |acceptable_key, acceptable_value|
        actual_key == acceptable_key && acceptable_value.matches?(actual_value)
      end
    end
  end

  # This is better than the default message only in that it lists acceptable headers.
  # An even better message would identify specific unacceptable headers.
  failure_message do |actual|
    "expected that #{actual} would match one of #{acceptable_headers}"
  end

end

它处理这些双阴性解决方案也处理的例子:

expect({ 'Content-Type' => "application/xml" }).to all_be_acceptable_headers
expect({ 'Content-Type' => "application/xml", 'Content-Length' => "123" }).to all_be_acceptable_headers
expect({ 'Content-Tape' => "application/xml" }).not_to all_be_acceptable_headers
expect({ 'Content-Type' => "not a content type" }).not_to all_be_acceptable_headers

如果缺少headers:键值对,你的双阴性解决方案会通过,我怀疑它不应该,但这可能永远不会发生。如果在NoMethodError上调用此匹配器会引发nil,如果不尽可能用户友好,则可能是正确的。同样,重点是让响应不是匹配器的问题更好。

这个匹配器还处理两种情况,你的双阴性解决方案没有:

  • 空标题哈希应该通过:

    expect({}).to all_be_acceptable_headers
    
  • RSpec的include有一个令人惊讶的行为(我在发现你的解决方案看起来不太合适的时候发现了这一点):in

    expect([0]).to include(0, 1)
    

    include被视为include_all_of,因此上述操作失败。但是在

    expect([0]).not_to include(0, 1)
    

    include被视为include_any_of,因此上述内容也失败了!

    因此,如果有多个可接受的标头,并且实际的标头散列有一个可接受的标头和一个不可接受的标头,则双负解决方案会通过。这个匹配器处理:

    expect({ 'Content-Type' => "not a content type", 'Content-Length' => "123" }).
      not_to all_be_acceptable_headers
    

答案 1 :(得分:0)

事实证明,现有的匹配器,否定的匹配器以及一些存在逻辑魔法都可以实现这一点。

以下是组件:

否定匹配者:

RSpec::Matchers.define_negated_matcher :does_not_include, :include
RSpec::Matchers.alias_matcher :a_hash_not_including, :does_not_include

接受标题:

let(:acceptable_headers) do
  {
    'Content-Type' => be_a(String)
  }
end

Spec“它只返回允许的标题”。此处的逻辑工程师现在知道这可以重写为“它不会返回未包含在允许的标题中的标题”。它就是这样:

  it 'includes only allowed headers' do
    expect(some_result).to match(
      a_hash_not_including(
        headers: a_hash_not_including(acceptable_headers)
      )
    )
  end