使用RSpec测试嵌套哈希时,如何匹配无序数组?
在RSpec中优雅处理嵌套哈希的无序数组比较
问题背景
你在RSpec测试里常碰到深度嵌套哈希的比较场景,比如{ foo: ["test", { bar: [1,2,3] }] }——其中从数据库读出来的数组[1,2,3]顺序没法保证,但测试时根本不需要关心顺序。之前的两种处理方式都有硬伤:
- 修改生产代码(比如在
MyClass.data里给数据库返回结果排序):平白影响生产逻辑 - 拆分断言逐个验证:测试代码变得繁琐又难读
你试过自定义继承Array的Bag类,重写eql?和==方法实现无序比较,但只有Bag在比较左侧时才生效;当实际值(数据库返回的普通数组)放在expect里、预期值是Bag实例时,会调用Array#==导致断言失败,哪怕加了[]和fetch方法也没用。
最优解决方案:用RSpec内置匹配器
完全没必要自己写自定义类,RSpec已经提供了专门的工具来处理这种场景,还完美支持嵌套结构:
1. 顶层无序数组直接用match_array
如果只是单独验证某个数组的无序匹配,直接用match_array即可:
expect(MyClass.data[:foo][1][:bar]).to match_array([1,2,3])
2. 嵌套场景结合include做整体验证
要一次性验证整个嵌套哈希结构,同时指定特定数组忽略顺序,可以结合include和哈希匹配语法:
expect(MyClass.data).to include( foo: [ "test", include(bar: match_array([1,2,3])) ] )
3. 非严格哈希匹配用hash_including
如果不需要校验哈希的所有键,只关心目标字段,用hash_including更简洁:
expect(MyClass.data).to include( foo: [ "test", hash_including(bar: match_array([1,2,3])) ] )
为什么自定义Bag类行不通?
RSpec的断言逻辑是拿实际值(数据库返回的普通Array)去和预期值比较,此时会调用实际值的==方法(也就是Array#==),而Array#==要求元素顺序完全一致——你的Bag类在右侧时,Array#==不会触发Bag重写的方法,所以必然断言失败。
进阶:自定义复用性匹配器
如果多个测试都要用到这种无序数组匹配,也可以自定义一个RSpec匹配器:
RSpec::Matchers.define :match_unordered_array do |expected| match do |actual| actual.sort == expected.sort end failure_message do |actual| "expected #{actual} to match unordered array #{expected}" end end
之后在测试里直接调用:
expect(MyClass.data[:foo][1][:bar]).to match_unordered_array([1,2,3])
内容的提问来源于stack exchange,提问作者23tux
相关产品推荐
相关产品推荐

