使用 FluentAssertions 框架在一个断言中评估所有属性



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))
);

最新更新