首页 > 解决方案 > 当类型是 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()但是,在这种情况下, in a的默认覆盖实现record实际上仅在两种类型相同时才有效,因此它会失败,因此 FluentAssertions 会在BeEquivalentTo().

并且,在失败消息中,FluentAssertions 通过 ToString() 将对象转换为字符串,令人困惑地报告了该问题。这是因为记录具有“值语义”,因此它会这样对待它们。在 GitHub 上有一个未解决的问题。

我确认如果我更改recordclass.

(我个人认为 FluentAssertions 应该忽略 Equals() 覆盖,当它位于 arecord并且两种类型不同时,因为这种行为可能不是人们所期望的。在发布时,当前问题与 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; }
    }
}

标签: c#.net-5fluent-assertions

解决方案


你试过用options.ComparingByMembers<Address>()吗?

尝试将您的测试更改为:partnerDto.Address.Should().BeEquivalentTo(partner.Address, o => o.ComparingByMembers<Address>());


推荐阅读