Shouldly 比较对象时,如何忽略集合的具体实现类型?
结论:需要区分 Shouldly 版本
Shouldly 4.x 的 ShouldBeEquivalentTo 要求运行时类型匹配,也没有可直接开启的“忽略集合具体类型”选项。即使将属性声明为 IReadOnlyCollection<Order>,断言仍会检查 List<Order> 与 ReadOnlyCollection<Order> 的类型差异。Shouldly 等价比较改造说明
如果可以使用预发布版本,5.0.0-preview.2 重写了等价比较引擎:普通集合按元素递归比较,不再区分集合容器的具体类型。这个场景可以沿用原来的断言,无需额外的忽略类型参数。版本发布说明、升级指南
原问题没有提供版本号,应先查看测试项目引用的 Shouldly 版本,再选择处理方式。
保留 4.x:在测试中统一集合类型
对于给出的 record,可以用 with 创建用于断言的副本,将两侧的 Orders 转为数组后,再比较整个响应对象:
using System.Linq;
using Shouldly;
var actualForAssertion = result with
{
Orders = result.Orders.ToArray()
};
var expectedForAssertion = expected with
{
Orders = expected.Orders.ToArray()
};
actualForAssertion.ShouldBeEquivalentTo(expectedForAssertion);
副本中参与断言的集合都是 Order[],因此可以避开当前的集合类型冲突。这样既不用修改 GetOrdersResponse 的属性声明,也不会替换原始对象上的 Orders。
这种写法仍会比较完整的 GetOrdersResponse,不必逐个断言属性。以后新增普通属性时,它们也会保留在副本中并参与等价比较。
with 是浅复制,这里只统一了 Orders 的容器类型。如果 Order 内部还有具体实现不同的集合,4.x 递归比较时仍可能遇到同样的问题。这个方法适合当前结构,不能作为任意对象图的通用忽略类型配置。
使用新版引擎:继续比较整个对象
在包含上述重写的 5.0.0-preview.2 中,可以保留模型和原来的断言:
result.ShouldBeEquivalentTo(expected);
两侧分别使用 List<Order> 和 ReadOnlyCollection<Order> 时,容器类型不同本身不再导致失败,集合元素仍会递归比较。普通集合默认要求顺序一致;忽略容器类型和忽略元素顺序是两条独立的规则。新版等价比较规则
5.0.0-preview.2 是预发布版本,升级还会改变部分断言的语义。采用前应查看升级指南并运行现有测试,不要将它当作仅调整集合类型检查的小补丁。发布说明
能否直接使用集合的 ShouldBe?
如果 Order 的相等性语义符合测试要求,也可以直接比较集合元素,无需调用 ToArray():
result.Orders.ShouldBe(expected.Orders);
ShouldBe 的集合比较允许两侧使用不同的集合实现,但它不等同于对任意复杂对象进行成员递归比较。对于没有合适相等性实现的普通引用类型,即使属性值相同,不同实例也可能比较失败。ShouldBe 官方文档
原问题没有给出 Order 的定义,无法保证这行代码能替代原来的深度等价断言。它也只检查 Orders,不会覆盖响应对象以后新增的其他属性。
当前错误只能说明断言遇到了类型差异,不能证明集合内容相同。消除类型冲突后,元素值、数量或顺序仍可能导致断言失败。
备注:内容仅供参考。
