如何用FluentAssertions测试Enumerable是否包含自身的‘子集’
优雅的FluentAssertions子集断言写法
需求场景
需要验证一个集合(actual)是否包含另一个集合(expected)作为属性子集:即预期集合的每个元素,都能在实际集合中找到对应的元素(实际元素可以包含更多属性,但指定的属性必须完全匹配)。示例测试代码如下:
[Fact] public void Test1() { // Arrange var expected = new[] { new {F1 = 1, F2 = "1" }, new {F1 = 2, F2 = "2" }, }; // Act var actual = new[] { new {F1 = 1, F2 = "1", F3 = true }, new {F1 = 2, F2 = "2", F3 = true }, new {F1 = 3, F2 = "3", F3 = true }, }; // Assert // 需要优雅的FluentAssertions表达式 }
失败的尝试
直接使用BeEquivalentTo并配置ExcludingMissingMembers会因为集合元素数量不匹配报错:
// Assert actual .Should() .BeEquivalentTo(expected, o => o.ExcludingMissingMembers());
错误提示:
Expected actual to be a collection with 2 item(s), but {{ F1 = 1, F2 = 1, F3 = True }, { F1 = 2, F2 = 2, F3 = True }, { F1 = 3, F2 = 3, F3 = True }} contains 1 item(s) more than {{ F1 = 1, F2 = 1 }, { F1 = 2, F2 = 2 }}.
可行但不够优雅的写法
以下写法可以实现需求,但可读性和流畅性不足:
- 先过滤实际集合再做等价比较:
actual .Where(a => expected.Any(e => e.F1 == a.F1 && e.F2 == a.F2)) .Should() .BeEquivalentTo(expected, o => o.ExcludingMissingMembers());
- 遍历预期集合逐个断言:
expected .ToList() .ForEach(expectedItem => actual .Should() .ContainEquivalentOf( expectation: expectedItem, config: o => o.ExcludingMissingMembers()) ); // 或者普通foreach写法 foreach (var expectedItem in expected) { actual .Should() .ContainEquivalentOf( expectation: expectedItem, config: o => o.ExcludingMissingMembers()); }
优雅的解决方案
使用FluentAssertions提供的AllSatisfy方法,结合ContainEquivalentOf实现流畅且可读的断言:
// Assert expected.Should().AllSatisfy(expectedItem => actual.Should().ContainEquivalentOf(expectedItem, o => o.ExcludingMissingMembers()), because: "expected is a 'subset' of actual");
说明
AllSatisfy:验证预期集合中的每个元素都满足后续断言逻辑,完全符合FluentAssertions的流畅风格;ContainEquivalentOf:检查实际集合中存在与预期元素属性匹配的项,ExcludingMissingMembers配置允许实际元素包含更多未在预期中定义的属性;- 通过
because参数添加断言失败时的提示信息,能大幅提升调试效率。
内容的提问来源于stack exchange,提问作者dani herrera
相关产品推荐
相关产品推荐

