Combination of ComparingByMember and IgnoreCyclicReferences doesn't work
See original GitHub issueDescription
If I keep it short: having override Equals
on objects I have to disable using it with ComparingByMembers
. But if I do so, the IgnoreCyclicReferences
seem to stop working.
Here the details.
We have written a lot of NHibernate persistence tests, where we are using FluentAssertions BeEquivalentOf
.
After adding a recommended for NH Equals-override in the DomainBase class we’ve got multiple issues with FluentAssertions.
We had to add a Configuration option ComparingByMembers<DomainBase>()
to prevent FluentAssertions using Equals-override for comparing objects equivalence. And it worked nice for the most of cases, but has introduced another issue - now FluentAssertions seem to ignore the option IgnoreCyclicReferences()
Complete minimal example reproducing the issue
Here is the minimal example I could make to reproduce the issue:
[TestFixture]
public class FluentAssertionsIssueTest {
public class Base {
public Guid Id { get; set; }
// NHibernate recommends overriding Equals for preventing issues with Sets and while working with multiple sessions, as well as issues with proxies in collections
public override bool Equals(object obj) {
if (ReferenceEquals(this, obj))
return true;
return (obj is Base baseObj) && baseObj.Id == Id;
}
}
public class Item : Base {
public string Title { get; set; }
public Item Previous { get; set; }
public Item Next { get; set; }
}
[Test]
public void IgnoreCyclicReferences_Broken() {
// imitating NHibernate save in one session
var first = new Item() {Id = Guid.NewGuid(), Title = "First"};
var second = new Item() {Id = Guid.NewGuid(), Title = "Second", Previous = first};
first.Next = second;
// imitating NHibernate load in another session
var firstCopy = new Item() {Id = first.Id, Title = first.Title};
var secondCopy = new Item() {Id = second.Id, Title = second.Title, Previous = firstCopy};
firstCopy.Next = secondCopy;
// I'd like to check, that all the properties could be persisted correctly.
// "The maximum recursion depth was reached" is generated here (IgnoringCyclicReferences doesn't seem to work)
firstCopy.Should().BeEquivalentTo(first, opt => opt.ComparingByMembers<Base>()
.IgnoringCyclicReferences(), "they are equivalent");
first.Title = "Changed";
// Removing Equals-override from Base class fixes FA issues, but introduces NHibernate-issues
// Removing ComparingByMembers-option removes "The maximum recursion depth was reached", but breaks this check (changed Title is not considered)
firstCopy.Should().NotBeEquivalentTo(first, opt => opt.ComparingByMembers<Base>()
.IgnoringCyclicReferences(), "after change");
}
}
Expected behavior:
Deep object comparison should be done, handling cyclic references correctly. The test should pass
Actual behavior:
I cannot use deep object comparison on objects with overridden Equals
and cyclic references.
The test fails with message:
The maximum recursion depth was reached.
The maximum recursion depth was reached.
The maximum recursion depth was reached.
The maximum recursion depth was reached.
With configuration:
- Use declared types and members
- Compare enums by value
- Ignoring cyclic references
- Match member by name (or throw)
- Without automatic conversion.
- Be strict about the order of items in byte arrays
bei FluentAssertions.Execution.LateBoundTestFramework.Throw(String message)
bei FluentAssertions.Execution.CollectingAssertionStrategy.ThrowIfAny(IDictionary`2 context)
bei FluentAssertions.Equivalency.EquivalencyValidator.AssertEquality(EquivalencyValidationContext context)
If I comment out override Equals
everything works in FA, but introduces issues with NHibernate.
If I remove options ComparingByMembers<Base>()
in both BeEquivalentTo
/ NotBeEquivalentTo
, then the difference of Title in firstCopy and first is not detected in the NotBeEquivalentTo
-Check.
Versions
- 5.10.3, 5.10.2
- Using .NET framework 4.6.1
Issue Analytics
- State:
- Created 3 years ago
- Comments:12 (7 by maintainers)
Top GitHub Comments
It’s a bug. The
EquivalencyValidator
does not treatItem
as a complex type because it overridesEquals
, and theIsCyclicReference
does not honor the overrides thatComparingByMembers
sets.This seems doable by changing
EquivalencyValidator.IsComplexType(object expectation)
fromto
The change above does not kick in when formatting a failed assertion, as
Formatter.ToString
does not have access toconfig
, so the output isIncluding the check for
Formatter.ToString
seems more possible once #1469 is in.