ObjectAssertions.Be no longer uses IComparable<T> and does not use IEquatable<T>
See original GitHub issueDescription
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:
- Created 2 years ago
- Comments:9 (5 by maintainers)
Top GitHub Comments
For vNext it seems we can make
ComparableAssertions
take anT : IComparable<T>
instead of anIComparable<T>
which would let us useEqualityComparer<T>.Default.Equals(T, T)
instead ofobject.Equals(object)
for[Not]Be
.The main benefit of
Equals(T)
overEquals(object)
is avoiding boxing value types. AsShould().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 ofEquals(T)
andEquals(object)
into account.The change in using
Equals
overCompareTo
forIComparable<T>
changed in #1177 but it seems we missed to mentioning it everywhere. You can useBeRankedEquallyTo
to compare the objects usingCompareTo
.