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.

ObjectAssertions.Be no longer uses IComparable<T> and does not use IEquatable<T>

See original GitHub issue

Description

Version 6.0.0 introduced an undocumented breaking change from version 5.10.3. In older versions, actual.Should().Be(expected) would pass when actual and expected were of the same type and that type implemented IComparable<T> and the CompareTo implementation returned 0. Tests that relied on this now fail in versions 6.0.0 and 6.1.0.

Interestingly, NUnit’s Assert.AreEqual fails to pass in this situation as well but it does pass if the class implements IEquatable<T> and the Equals implementation returns true.

Neither the older or newer versions of FluentAssertions will pass Should().Be() for the IEquatable<T> scenario.

Complete minimal example reproducing the issue

using FluentAssertions;
using NUnit.Framework;

namespace FluentAssertionBug
{
    public class ClassToBeComparedWithIComparable : IComparable<ClassToBeComparedWithIComparable>
    {
        public string Id { get; set; }
        public int Value { get; set; }

        public int CompareTo(ClassToBeComparedWithIComparable other)
        {
            var c = string.Compare(Id, other.Id, StringComparison.Ordinal);


            return c != 0 ? c : Value.CompareTo(other.Value);
        }
    }

    public class ClassToBeComparedWithIEquatable : IEquatable<ClassToBeComparedWithIEquatable>
    {
        public string Id { get; set; }
        public int Value { get; set; }

        public bool Equals(ClassToBeComparedWithIEquatable other)
        {
            if (other == null)
                return false;

            return Id.Equals(other.Id) && Value.Equals(other.Value);
        }
    }

    [TestFixture]
    public class Tests
    {
        [Test]
        public void GivenTwoIComparablesThatHaveIdenticalValues_ShouldBe_ShouldPass_WillFailInNewerVersions()
        {
            var value1 = new ClassToBeComparedWithIComparable { Id = "One", Value = 1 };
            var value2 = new ClassToBeComparedWithIComparable { Id = "One", Value = 1 };

            value1.Should().Be(value2);
        }

        [Test]
        public void GivenTwoIEquatablesThatHaveIdenticalValues_Equals_ShouldPass()
        {
            var value1 = new ClassToBeComparedWithIEquatable { Id = "One", Value = 1 };
            var value2 = new ClassToBeComparedWithIEquatable { Id = "One", Value = 1 };

            if (value1.Equals(value2))
                Assert.Pass();

            Assert.Fail();
        }

        [Test]
        public void GivenTwoIEquatablesThatHaveIdenticalValues_AssertAreEqual_ShouldPass()
        {
            var value1 = new ClassToBeComparedWithIEquatable { Id = "One", Value = 1 };
            var value2 = new ClassToBeComparedWithIEquatable { Id = "One", Value = 1 };

            Assert.AreEqual(value2, value1);
        }
    }
}

Expected behavior:

I expect the breaking change for the IComparable<T> scenario to be documented. I expect Should().Be() to honor the IEquatable<T> implementation when determining if 2 objects are equal.

Actual behavior:

Neither the 6.0.0 nor the 6.1.0 documentation mention that Should().Be() no longer honors the IComparable<T> implementation when determining equality.

Should().Be() does not honor the IEquatable<T> implementation when determining equality.

Versions

  • I upgraded from FluentAssertion 5.10.3 to 6.1.0. (I also tried using 6.0.0 to determine when the break first occurred.)
  • I am using .Net Core 3.1
  • I am using NUnit 3.13.2 as my testing suite.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:9 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
jnyrupcommented, Sep 5, 2021

For vNext it seems we can make ComparableAssertions take an T : IComparable<T> instead of an IComparable<T> which would let us use EqualityComparer<T>.Default.Equals(T, T) instead of object.Equals(object) for [Not]Be.

0reactions
jnyrupcommented, Sep 5, 2021

The main benefit of Equals(T) over Equals(object) is avoiding boxing value types. As Should().Be(object) already boxes the any value type, the remaining performance is avoiding a type check, which should be negligible. I have a hard time imagining that we should take inconsistent implementations of Equals(T) and Equals(object) into account.

The change in using Equals over CompareTo for IComparable<T> changed in #1177 but it seems we missed to mentioning it everywhere. You can use BeRankedEquallyTo to compare the objects using CompareTo.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Have to manually implement IEquatable<T> but not ...
For some reason, I can no longer use MyInheritedClass as a generic type for the custom list unless I manually make it implement...
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