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

Evaluate all properties in one assertion with the FluentAssertions framework

上下文:

我们需要断言具有许多属性的对象响应,这些属性具有许多排列,其中相当多的属性是动态的(生成的 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 我知道这可以通过自定义 RuleBuilder 来完成,并且我熟悉 FluentValidation,但我想保留它作为最后的手段。

我看到一个选择是使用 AssertionScope

    public static void Main()
    {
        using (new AssertionScope())
        {
            ActualHouseResponse
                .Should()
                .BeEquivalentTo(ExpectedHouseResponse, config => 
                    config.Excluding(x => x.Doors));
        
            ActualHouseResponse.Doors.Should().BeGreaterThan(0);
        }
    }

这将 运行 在停止执行之前的所有断言。

但是这并不强制评估 ActualHouseResponse 的所有 属性。

您可以使用 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

对于那些感兴趣的人,这是我根据 Jonas's 答案

使用的解决方案
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))
);