如何解决当类型是 C# 9 记录时,FluentAssertions Should().BeEquivalentTo() 在微不足道的情况下失败,似乎将对象视为字符串
我最近开始使用 FluentAssertions,它应该有这个强大的对象图比较功能。
我正在尝试做可以想象到的最简单的事情:将 Address
对象的属性与 AddressDto
对象的属性进行比较。它们都包含 4 个简单的字符串属性:Country、City、Street 和 ZipCode(它不是生产系统)。
有人可以向我解释一下,就像我两岁一样,出了什么问题?
partnerDto.Address.Should().BeEquivalentTo(partner.Address)
它失败并显示此消息:
留言:
预期结果。地址为 4 Some street,12345 Toronto,Canada,但发现 AddressDto { Country = Canada,ZipCode = 12345,City = Toronto,Street = 4 Some street }。
有配置:
它似乎试图将 Address
对象视为字符串(因为它覆盖了 ToString()
?)。我尝试使用 options.ComparingByMembers<AddressDto>()
选项,但似乎没什么区别。
(AddressDto
是一个 record
顺便说一句,不是 class
,因为我正在用这个项目测试新的 .Net 5 功能;但它可能没有区别.)
故事的寓意:
使用 record
而不是 class
会触发 FluentAssertions,因为记录会在后台自动覆盖 Equals()
,并且 FluentAssertions 假定它应该使用 Equals()
而不是属性比较,因为覆盖的 Equals()
可能是为了提供所需的比较。
但是,在这种情况下,Equals()
中 record
的默认覆盖实现实际上仅在两种类型相同时才有效,因此它失败,因此 FluentAssertions 在 {{1 }}。
而且,在失败消息 FluentAssertions 中,通过 ToString() 将对象转换为字符串,混淆地报告了这个问题。这是因为记录具有“值语义”,因此它如此对待它们。有一个 open issue about this on GitHub。
我确认将 BeEquivalentTo()
更改为 record
不会出现问题。
(我个人认为 FluentAssertions 在 class
上时应该忽略 Equals() 覆盖并且这两种类型不同,因为这种行为可以说不是人们所期望的。当前的问题,在发布时间,属于 FluentAssertions 版本 5.10.3。)
我编辑了我的问题标题以更好地代表问题的实际情况,因此它可能对人们更有用。
参考:
正如人们所问,这里是域实体的定义(为了简洁起见,必须删除一些方法,因为我正在做 DDD,但它们肯定与问题无关):
record
这里是 Dto 的等价物:
public class Partner : MyEntity
{
[required]
[StringLength(PartnerInvariants.NameMaxLength)]
public string Name { get; private set; }
[required]
public Address Address { get; private set; }
public virtual IReadOnlyCollection<Transaction> Transactions => _transactions.AsReadOnly();
private List<Transaction> _transactions = new List<Transaction>();
private Partner()
{ }
public Partner(string name,Address address)
{
UpdateName(name);
UpdateAddress(address);
}
...
public void UpdateName(string value)
{
...
}
public void UpdateAddress(Address address)
{
...
}
...
}
public record Address
{
[required,MinLength(1),MaxLength(100)]
public string Street { get; init; }
[required,MaxLength(100)]
public string City { get; init; }
// As I mentioned,it's not a production system :)
[required,MaxLength(100)]
public string Country { get; init; }
[required,MaxLength(100)]
public string ZipCode { get; init; }
private Address() { }
public Address(string street,string city,string country,string zipcode)
=> (Street,City,Country,ZipCode) = (street,city,country,zipcode);
public override string ToString()
=> $"{Street},{ZipCode} {City},{Country}";
}
解决方法
您是否尝试过使用 options.ComparingByMembers<Address>()
?
尝试将您的测试更改为:partnerDto.Address.Should().BeEquivalentTo(partner.Address,o => o.ComparingByMembers<Address>());
我认为 the docs 的重要部分是:
要确定 Fluent Assertions 是否应该递归到对象的属性或字段中,它需要了解哪些类型具有值语义以及哪些类型应该被视为引用类型。默认行为是将覆盖 Object.Equals 的每个类型视为具有值语义的对象
您的两个记录都覆盖了 Equals
,但它们的 Equals 方法仅在另一个对象是相同类型时才返回 true。所以我认为 Should().BeEquivalentTo
是看到您的对象实现了它们自己的相等性,调用(大概)返回 false 的 AddressDto.Equals
,然后报告失败。
它使用两条记录的 ToString()
版本报告失败,返回 { Country = Canada,ZipCode = 12345,City = Toronto,Street = 4 Some street }
(对于没有覆盖 ToString 的记录)和 4 Some street,12345 Toronto,Canada,
(对于具有覆盖 ToString 的对象) ).
正如文档所说,您应该能够使用 ComparingByMembers
来覆盖它:
partnerDto.Address.Should().BeEquivalentTo(partner.Address,options => options.ComparingByMembers<Address>());
或全局:
AssertionOptions.AssertEquivalencyUsing(options => options
.ComparingByMembers<Address>());
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。