question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

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

See original GitHub issue

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

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:12 (7 by maintainers)

github_iconTop GitHub Comments

2reactions
Gorthogcommented, Aug 29, 2018

Thanks for the detailed report! Excellent investigation

2reactions
dennisdoomencommented, Aug 28, 2018

Wow. Good catch @jnyrup

Read more comments on GitHub >

github_iconTop Results From Across the Web

FluentAssertions Should().BeEquivalentTo() fails in trivial ...
It seems it tries to treat the Address object as a string (because it overrides ToString() ?). I tried to use the options.ComparingByMembers< ......
Read more >
Object graph comparison
By default, Should(). BeEquivalentTo() compares Enum members by the enum's underlying numeric value. An option to compare an Enum only by name is...
Read more >
Fluent Assertions 5.0: The best unit test assertion library in the ...
You'll now find that all assertions start with Should() , e.g.. object. ... The changes to BeEquivalentTo will be the most visible ones:....
Read more >
Just Use FluentAssertions - Tinkerer
Option 1: Implementing Object.Equals for use in tests​​ Now, I'm not a huge fan of implementing Object. Equals() in general, and I've written...
Read more >
Improving Unit Tests with Fluent Assertions
In this article, we are going to learn how we can improve our unit tests using Fluent Assertions library with .NET project.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found