使用 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))
);
上下文:
我们需要断言具有许多属性的对象响应,这些属性具有许多排列,其中相当多的属性是动态的(生成的 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))
);