Context:
我们需要使用许多具有许多排列的属性断言对象响应,其中相当多的属性是动态的(生成的 GUID 等)。
示例方案
使用FluentAssertions时...Should().BeEquivalentTo(...)
能够在一次评估中获取所有不匹配字段的列表。
所以给定 (C#) 代码:
using System;
using FluentAssertions;
public class Program
{
public class HouseResponse
{
public int Windows { get; set; }
public int Bedrooms { get; set; }
public int Doors { get; set; }
public int Bathrooms { get; set; }
}
public static readonly HouseResponse ExpectedHouseResponse = new HouseResponse
{
Windows = 10,
Bedrooms = 5,
Doors = 2,
Bathrooms = 2
};
public static readonly HouseResponse ActualHouseResponse = new HouseResponse
{
Windows = 10,
Bedrooms = 5,
Doors = 3,
Bathrooms = 3
};
public static void Main()
{
ActualHouseResponse
.Should()
.BeEquivalentTo(ExpectedHouseResponse);
}
}
如果有 2 个属性不匹配,则单个断言的输出为:
Unhandled exception. FluentAssertions.Execution.AssertionFailedException: Expected property root.Doors to be 2, but found 3.
Expected property root.Bathrooms to be 2, but found 3.
这非常方便,因为您可以在一条错误消息中获取所有故障。
但是对于部分匹配,假设我们期望门数不同但始终是有效数字> 0,我们必须这样做:
public static void Main()
{
ActualHouseResponse
.Should()
.BeEquivalentTo(ExpectedHouseResponse, config =>
config.Excluding(x => x.Doors));
ActualHouseResponse.Doors.Should().BeGreaterThan(0);
}
这实际上不会击中ActualHouseResponse.Doors.Should().BeGreaterThan(0);
断言,因为我们已经在.Should().BeEquivalentTo
上失败了.Bathrooms
因为这不是一个匹配项。
因此,目标是能够一次性评估所有属性。这将:
- 强制评估所有属性。
- 允许我们在一次测试运行中获取所有失败属性的摘要(而不必修复一个属性,运行测试,然后查看下一个失败的位置等)
大致如下:
public static void Main()
{
ActualHouseResponse
.Should()
.BeEquivalentTo(ExpectedHouseResponse, config =>
config.OverideEvaluation(x => x.Doors, doors => doors > 0));
}
有没有人有任何想法,或者可能偶然发现了一些我可能错过的FluentAssertions文档?
P.S我知道这可以通过自定义规则生成器来完成,并且熟悉FluentValidation,但希望将其作为最后的手段。
您可以使用Using
/When
组合来指示等效引擎如何比较某些属性。
ActualHouseResponse.Should().BeEquivalentTo(ExpectedHouseResponse, opt => opt
.Using<int>(ctx => ctx.Subject.Should().BeGreaterThan(0))
.When(e => e.Path.EndsWith(nameof(HouseResponse.Doors)))
);
https://fluentassertions.com/objectgraphs/#equivalency-comparison-behavior
我看到一个选项是使用AssertionScope
public static void Main()
{
using (new AssertionScope())
{
ActualHouseResponse
.Should()
.BeEquivalentTo(ExpectedHouseResponse, config =>
config.Excluding(x => x.Doors));
ActualHouseResponse.Doors.Should().BeGreaterThan(0);
}
}
这将在停止执行之前运行所有断言。
但这并不强制要求评估 ActualHouseResponse的所有属性。
对于那些感兴趣的人,这是我根据乔纳斯的答案选择的解决方案
public static class EquivalencyAssertionOptionsExtensions
{
public static EquivalencyAssertionOptions<TSource> Override<TSource, TProperty>(
this EquivalencyAssertionOptions<TSource> options,
Expression<Func<TSource, TProperty>> propertyAccessor,
Action<TProperty> assertion)
{
var memberExpression = (MemberExpression) propertyAccessor.Body;
var propertyName = memberExpression.Member.Name;
return options
.Using<TProperty>(ctx => assertion(ctx.Subject))
.When(x => x.SelectedMemberPath.Equals(propertyName));
}
}
这让我做
ActualHouseResponse.Should().BeEquivalentTo(ExpectedHouseResponse, opt => opt
.Override(x => x.Doors, doors => doors.Should().BeGreaterThan(0))
.Override(x => x.Windows, windows => windows.Should().BeLessThan(10))
);