最近我开始使用FluentAssertions, 它应该具备强大的对象图比较功能。
我想要做的事情非常简单:比较一个 Address
对象和一个 AddressDto
对象的属性。它们都包含四个简单的字符串属性: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()
可能就是为了提供所需的比较。
但是,在这种情况下,record
中默认的重写实现仅在两个类型相同时才起作用,因此失败,因此 FluentAssertions 报告了 BeEquivalentTo()
的错误。
而且,在失败消息中,FluentAssertions 通过 ToString() 将对象混淆报告问题。 这是因为记录具有“值语义”,因此将它们视为这样处理。 关于此问题,GitHub 上有一个问题。
我确认如果我将 record
更改为 class
,则不会出现此问题。
(我个人认为,当 Equals() 在一个 record 上,并且两个类型不同时,FluentAssertions 应该忽略它,因为这种行为可能不是人们所期望的。当前发布时的版本为 FluentAssertions 5.10.3。)
我修改了我的问题标题,以更好地表示实际问题,这样可以更有用。
参考文献:
如有人问,这是领域实体的定义(为了简洁起见,我删除了一些方法,因为我正在进行 DDD,但是它们肯定与该问题无关):
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, MinLength(1), MaxLength(100)]
public string City { get; init; }
// As I mentioned, it's not a production system :)
[Required, MinLength(1), MaxLength(100)]
public string Country { get; init; }
[Required, MinLength(1), 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}";
}
这里是Dto的等效内容:
public record PartnerDetailsDto : IMapFrom<Partner>
{
public int Id { get; init; }
public string Name { get; init; }
public DateTime CreatedAt { get; init; }
public DateTime? LastModifiedAt { get; init; }
public AddressDto Address { get; init; }
public void Mapping(Profile profile)
{
profile.CreateMap<Partner, PartnerDetailsDto>();
profile.CreateMap<Address, AddressDto>();
}
public record AddressDto
{
public string Country { get; init; }
public string ZipCode { get; init; }
public string City { get; init; }
public string Street { get; init; }
}
}
Address
和AddressDto
的定义包含进去? - canton7partnerDto
变量的类型是什么?Address
属性如何定义? - Pavel Anikhouski