ruby 如何在rspec中使用`eq` matcher和`hash_including` matcher来处理哈希数组

jexiocij  于 12个月前  发布在  Ruby
关注(0)|答案(3)|浏览(82)

我有一个哈希数组,我试图Assert这个数组有确切的一定数量的哈希,按照一定的顺序,有特定的键
假设我有一个水果数组。

fruits = [
  { name: 'apple', count: 3 },
  { name: 'orange', count: 14 },
  { name: 'strawberry', count: 7 },
]

当我将eq匹配器与hash_including(或其别名include)一起使用时,Assert失败。

# fails :(
expect(fruits).to eq([
  hash_including(name: 'apple'),
  hash_including(name: 'orange'),
  hash_including(name: 'strawberry'),
])

很奇怪,这不起作用,我总是找到一种方法绕过它,继续前进,但它已经困扰了我一段时间,所以我决定这次发布它。

我不想找的是

显然这是可行的,但我喜欢其他语法,因为这是这些匹配器的重点:所以我不需要手工转换我的数据结构,并且有更多可读的规范。

fruit_names = fruits.map { |h| h.fetch(:name) }
expect(fruit_names).to eq(['apple', 'orange', 'strawberry'])

contain_exactlyinclude可以工作,但我关心数组的确切大小和元素的顺序,它们无法Assert。

# passes but doesn't assert the size of the array or the order of elements
expect(fruits).include(
  hash_including(name: 'apple'),
  hash_including(name: 'orange'),
  hash_including(name: 'strawberry'),
)

# passes but doesn't assert the exact order of elements
expect(fruits).contain_exactly(
  hash_including(name: 'apple'),
  hash_including(name: 'orange'),
  hash_including(name: 'strawberry'),
)
5lwkijsr

5lwkijsr1#

看起来你只需要使用match

fruits = [
  { name: 'apple', count: 3 },
  { name: 'orange', count: 14 },
  { name: 'strawberry', count: 7 },
]

expect(fruits).to match([
  include(name: 'apple'),
  include(name: 'orange'),
  include(name: 'strawberry'),
])

如果某个数组元素丢失或多余,此测试将失败
如果某些哈希不包括指定的键值对,则此测试将失败
如果数组元素顺序错误,则此测试将失败

vi4fp9gy

vi4fp9gy2#

我不记得有任何内置的匹配器可以同时检查包含和顺序(UPD)。I'm wrong),同时在匹配值方面很灵活,但是如果你经常需要这些检查,你可以很容易地创建一个简单的自定义匹配器。
最基本的版本非常简单:

RSpec::Matchers.define :contain_exactly_ordered do |expected_collection|
  match do |actual|
    expected_collection.each_with_index.all? do |expected_element, index|
      values_match?(expected_element, actual[index])
    end
  end
end

然后

specify do
  fruits = [
    { name: 'apple', count: 3 },
    { name: 'orange', count: 14 },
    { name: 'strawberry', count: 7 },
  ]

  expect(fruits).to contain_exactly_ordered([
    hash_including(name: 'apple'),
    hash_including(name: 'orange'),
    hash_including(name: 'strawberry'),
  ]) #=> This should be green
end

但是有一个严重的缺点-如果顺序错误,这个匹配器不能提供足够的信息来轻松修复错误(它没有说 * 哪些元素 * 是不匹配的-对于2-3个元素来说不是什么大问题,但对于更大的集合,这可能是一个痛苦)。因此,理想情况下,我们需要调整失败信息,使其真正有帮助。类似于以下内容:

RSpec::Matchers.define :contain_exactly_ordered do |expected_collection|
  match do |actual|
    @mismatched_items = expected_collection.each_with_index.reject do |expected_element, index|
      values_match?(expected_element, actual[index])
    end

    @mismatched_items.none?
  end

  failure_message do
    @mismatched_items.map do |expectation, i|
      "  [#{i}]: expected #{expectation.description} to match #{actual[i].inspect}"
    end.unshift("Mismatched items found at the following indices:").join("\n")
  end
end

如果我们打乱了顺序:

specify do
  fruits = [
    { name: 'apple', count: 3 },
    { name: 'orange', count: 14 },
    { name: 'strawberry', count: 7 },
  ]

  expect(fruits).to contain_exactly_ordered([
    hash_including(name: 'orange'),
    hash_including(name: 'apple'),
    hash_including(name: 'strawberry'),
  ])
end

我们的错误消息非常有用:

1) ... should contain exactly ordered hash_including(:name=>"orange"), hash_including(:name=>"apple"), and hash_including(:name=>"strawberry")
     Failure/Error:
       expect(fruits).to contain_exactly_ordered([
         hash_including(name: 'orange'),
         hash_including(name: 'apple'),
         hash_including(name: 'strawberry'),
       ])
     
       Mismatched items found at the following indices:
         [0]: expected hash_including(:name=>"orange") to match {:name=>"apple", :count=>3}
         [1]: expected hash_including(:name=>"apple") to match {:name=>"orange", :count=>14}
ruoxqz4g

ruoxqz4g3#

你做的有点反模式。您几乎不应该编写具有这种复杂程度的测试,或者将多个测试捆绑到一个期望中。这样做会使结果的粒度变小,并使测试更难识别问题。
你最好对每个结果有不同的规范,或者只是寻找预期的数据或结构不变的完整数据结构。举例来说:

describe "#fruits" do
  let(:fruits) do
    [
      { name: 'apple', count: 3 },
      { name: 'orange', count: 14 },
      { name: 'strawberry', count: 7 },
    ]
  end

  it "should contain an apple" do
    expect(fruits.keys).to inlcude("apple")
  end

  it "should contain an orange" do
    expect(fruits.keys).to inlcude("orange")
  end

  it "should contain an apple" do
    expect(fruits.keys).to inlcude("strawberry")
  end
end

否则,如果你想要整个结构,只要确保它是有序的。数组是有序的,并且需要被排序为相等。哈希值保证插入顺序,但如果它们包含相同的内容,则无论顺序如何都是相等的,因此您需要比较:

# under your "#fruits" tests...
  describe "sorted fruits" do     
    let(:sorted_fruits) { fruits.sort_by { _1[:name] } }

    it "should have the same structure as the original array of hashes" do
      expect(fruits).to eql(sorted_fruits)
    end

    it "should contain an apple as the first fruit" do
      expect(sorted_fruits.first).to eql("apple")
    end
  end

当然,还有其他方法可以做到这一点,但关键是您希望您的测试是DAMP,而不是DRY,并使您的测试尽可能不受新逻辑的影响。否则,很可能会给测试带来复杂性,因为它会使用未经测试的测试逻辑,而不是简单地测试被测对象。YMMV.

相关问题