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

Ruby 在RSpec中,如何期望头包含在可接受值列表中?,ruby,unit-testing,rspec,Ruby,Unit Testing,Rspec,我正在尝试编写一个规范,该规范断言来自API调用的HTTP头都包含在可接受头的列表中(也包含可接受的值) 我最后写了这样的东西: expect(response.headers).to all(be_included_in(acceptable_headers)) 其中,be\u include\u in是一个自定义匹配器: RSpec::Matchers.define :be_included_in do |enumerable| match do |element| enume

我正在尝试编写一个规范,该规范断言来自API调用的HTTP头都包含在可接受头的列表中(也包含可接受的值)

我最后写了这样的东西:

expect(response.headers).to all(be_included_in(acceptable_headers))
其中,
be\u include\u in
是一个自定义匹配器:

RSpec::Matchers.define :be_included_in do |enumerable|
  match do |element|
    enumerable.include?(element)
  end
end
这对于断言头都在包含的范围内非常有效,但不满足测试其值以供接受的要求


你知道如何优雅地做到这一点吗?

事实证明,通过现有的匹配器、否定匹配器和一点存在逻辑魔法,这是可能的

以下是组件:

否定匹配器:

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
规范“它只返回允许的标题”。这里的逻辑学家现在知道,这可以重写为“它不返回未包含在允许的头中的头”。就这样:

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

这里有一个解决方案,它将您最初尝试的风格与根据Header Name=>RSpec matcher的散列检查实际头的想法结合起来。它实现了以下功能:

  • 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
如果
头:
键值对丢失,则您的双负解决方案将通过,我怀疑这不应该发生,尽管这可能永远不会发生。如果在
nil
上调用此匹配器,则会引发
NoMethodError
,如果不尽可能友好,这可能是正确的。同样,主要的一点是,最好的回答不是匹配者的问题

此匹配器还处理两种双负解决方案无法处理的情况:

  • 空标头哈希应传递:

    expect({}).to all_be_acceptable_headers
    
  • RSpec的
    include
    有一个令人惊讶的行为(我在找出您的解决方案似乎不太正确的原因时发现了这一点):在

    include
    被视为
    include\u all\u of
    ,因此上述操作失败。但是在

    expect([0]).not_to include(0, 1)
    
    include
    被视为
    include\u any\u of
    ,因此上述操作也失败

    因此,如果有多个可接受的头,并且实际的头哈希有一个可接受的头和一个不可接受的头,则双负解决方案将通过。此匹配器处理以下内容:

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

每个可接受的标头是否都有一组已知的可接受值,或者可以通过简单的方法(如每个可接受标头的regexp)来识别可接受值,或者是否需要更复杂的方法?还有,
在您的匹配器中输入错误?接受的值非常简单,可以用RSpec匹配器表示(尽管也有用于复杂条件的RSpec匹配器…)。事实上,我想出了一个解决方案,我会将其作为一个答案发布,以获得反馈。你说得对,
是一个输入错误!如果可接受标头列表中的标头不存在(与具有不正确的值相反),测试是否应通过?是的,其想法是“仅”返回可接受的标头。也就是说
包含?(header)=>可接受(header)
,因此对于
不包含?(header)或可接受(header)
,这是正确的。
expect({ 'Content-Type' => "not a content type", 'Content-Length' => "123" }).
  not_to all_be_acceptable_headers