Skip to content

Adding to Should().BeEquivalentTo() options.WithTracing() changes outcome of the test #903

@Gorthog

Description

@Gorthog

Description

I got an error message which I think should not have happened, since the only difference is the order of the item and by default order shouldn't break equivalence. So I tried adding options.WithTracing() to understand what's going on, but then the test passes. This is the output without WithTracing():

Message: Expected item[0] to be 

Deal.DealComputer+Deal
{
   Items = {Deal.DealComputer+SingleItem
      {
         Name = "Croissant"
         Price = 12
         Tags = {Pastry}
      }, Deal.DealComputer+SingleItem
      {
         Name = "Cappuccino"
         Price = 11
         Tags = {HotDrink}
      }}
   Name = "Coffee + Pastry"
   Price = 19
   Tags = {HotDrink, Pastry}
}, but found 

Deal.DealComputer+Deal
{
   Items = {Deal.DealComputer+SingleItem
      {
         Name = "Cappuccino"
         Price = 11
         Tags = {HotDrink}
      }, Deal.DealComputer+SingleItem
      {
         Name = "Croissant"
         Price = 12
         Tags = {Pastry}
      }}
   Name = "Coffee + Pastry"
   Price = 19
   Tags = {HotDrink, Pastry}
}.

With configuration:
- Use declared types and members
- Compare enums by value
- Include all non-private properties
- Include all non-private fields
- Match member by name (or throw)
- Without automatic conversion.
- Without automatic conversion.
- Be strict about the order of items in byte arrays

Complete minimal example reproducing the issue

public enum Tag
{
    HotDrink,
    Pastry,
    Breakfast
}

public interface Dish
{
    string Name { get; set; }
    int Price { get; set; }
    IEnumerable<Tag> Tags { get; set; }
}

public class SingleItem : Dish, IEquatable<SingleItem>
{
    public string Name { get; set; }
    public int Price { get; set; }
    public IEnumerable<Tag> Tags { get; set; }

    public override bool Equals(object obj)
        => Equals(obj as SingleItem);

    public bool Equals(SingleItem other) 
        => !(other is null) &&
           Name == other.Name &&
           Price == other.Price &&
           Tags.SequenceEqual(other.Tags);

    public override int GetHashCode() 
        => HashCode.Combine(Name, Price, Tags);

    public static bool operator ==(SingleItem item1, SingleItem item2)
    {
        if (item1 is null)
        {
            return item2 is null;
        }

        return item1.Equals(item2);
    }

    public static bool operator !=(SingleItem item1, SingleItem item2)
        => !(item1 == item2);
}

public class Deal : Dish, IEquatable<Deal>
{
    string Name { get; set; }
    int Price { get; set; }
    IEnumerable<Tag> Tags { get; set; }
}

public class SingleItem : Dish, IEquatable<SingleItem>
{
    public string Name { get; set; }
    public int Price { get; set; }
    public IEnumerable<Tag> Tags { get; set; }

    public override bool Equals(object obj)
        => Equals(obj as SingleItem);

    public bool Equals(SingleItem other) 
        => !(other is null) &&
           Name == other.Name &&
           Price == other.Price &&
           Tags.SequenceEqual(other.Tags);

    public override int GetHashCode() 
        => HashCode.Combine(Name, Price, Tags);

    public static bool operator ==(SingleItem item1, SingleItem item2)
    {
        if (item1 is null)
        {
            return item2 is null;
        }

        return item1.Equals(item2);
    }

    public static bool operator !=(SingleItem item1, SingleItem item2)
        => !(item1 == item2);
}

public class Deal : Dish, IEquatable<Deal>
{
    public string Name { get; set; }
    public int Price { get; set; }
    public IEnumerable<Tag> Tags { get; set; }
    public IEnumerable<SingleItem> Items { get; set; }

    public override bool Equals(object obj) => Equals(obj as Deal);

    public bool Equals(Deal other)
    {
        return !(other is null) &&
               Name == other.Name &&
               Price == other.Price &&
               Tags.SequenceEqual(other.Tags) &&
               Items.SequenceEqual(other.Items);
    }

    public override int GetHashCode() => HashCode.Combine(Name, Price, Tags, Items);

    public static bool operator ==(Deal deal1, Deal deal2)
    {
        if (deal1 is null)
        {
            return deal2 is null;
        }

        return deal1.Equals(deal2);
    }

    public static bool operator !=(Deal deal1, Deal deal2) => !(deal1 == deal2);
}

readonly SingleItem cappuccino = new SingleItem { Name = "Cappuccino", Price = 11, Tags = new[] { Tag.HotDrink }};
readonly SingleItem croissant = new SingleItem { Name = "Croissant", Price = 12, Tags = new[] { Tag.Pastry } };
readonly SingleItem tea = new SingleItem { Name = "Tea", Price = 8, Tags = new[] { Tag.HotDrink } };
readonly SingleItem datesPastry = new SingleItem { Name = "Dates Pastry", Price = 10, Tags = new[] { Tag.Pastry } };
readonly Deal CoffeeAndPastryDeal = new Deal { Name = "Coffee + Pastry", Price = 19, Tags = new [] { Tag.HotDrink, Tag.Pastry } };

[Fact]
public void FourItemsSingleDeal()
{
    var items = new[] {
        tea,
        datesPastry,
        cappuccino,
        croissant,
    };

    var deals = new[] { CoffeeAndPastryDeal };

    var completedDeal1 = CoffeeAndPastryDeal.DeepClone();
    completedDeal1.Items = new[] { croissant, cappuccino };
    var expected = new Dish[]
    {
        completedDeal1,
        tea,
        datesPastry
    };

    var completedDeal2 = CoffeeAndPastryDeal.DeepClone();
    completedDeal2.Items = new[] { cappuccino, croissant };

    var actual =
        new Dish[]
    {
        completedDeal2,
        tea,
        datesPastry
    };

    actual.Should().BeEquivalentTo(expected, o => o.WithTracing()); // passes
    actual.Should().BeEquivalentTo(); //fails
}

Expected behavior:

I expect options.WithTracing() to not affect the outcome of tests

Actual behavior:

options.WithTracing() affects the outcome of tests

Versions

  • Which version of Fluent Assertions are you using? 5.4.1
  • Which .NET runtime and version are you targeting? .net core 2.1
  • For deep cloning I'm using nuget DeepCloner 0.10.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions