0

I have an IEnumerable<MyClass> returned from a foo method. MyClass contains a nested class and typically looks like below. The issue is Assert always returns false.

MyClass
{
    SomeNestedClass
    {
        public decimal  decimalField1;
        public int      intField2;
    }
}

I am writing a Unit test to test the Foo method by

// Arrange
var expectedItem = new MyClass 
{
    new SomeNestedClass
    {
        decimalField1 = 10M,
        intField2 = 20
    }
}

// Act
var results = sut.Foo();

// Assert
Assert.IsTrue(results.Contains(expectedItem));

The problem is Assert is always false.

Pac0
  • 21,465
  • 8
  • 65
  • 74
JaisG
  • 147
  • 1
  • 8
  • 1
    probably not comparing the same object. you can use FirstOrDefault(x=>x.decimalField == expectemItem.decimalField && x.intField2 == expectedItem.inteField2) – Gilad Aug 19 '20 at 21:27
  • 1
    Reference equality is the default. Just because two things look the same *to you* doesn't mean that .NET considers them equal. – Ben Voigt Aug 19 '20 at 21:27

2 Answers2

3

We do not have enough info there (implementation of sut.Foo();) but I suspect you have to override the Object.Equals() method in the MyClass type. Without that, the Contains() funtion will only check against the references equality.

Romka
  • 144
  • 8
3

Contains will use the default equality comparer for the type when trying to find a match, which is a reference comparison for reference types like classes. Since we know that there is no reference to the object you just created in the list, this will not work.

One simple way around this is to use the System.Linq method Any(), which returns true if any items in an IEnumerable meet the specified conditions.

For example:

Assert.IsTrue(results.Any(result => 
    result.decimalField1 == expectedItem.decimalField1 &&
    result.intField2 == expectedItem.intField2));

A slightly more laborious but re-usable way to do this is to write a SomeNestedClass comparer that can be passed to the Contains method (I'm using SomeNestedClass even though result was declared as a MyClass in the example because that's the only class that has fields we can use for comparison):

public class SomeNestedClassComparer : IEqualityComparer<MyClass.SomeNestedClass>
{
    public bool Equals(MyClass.SomeNestedClass x, MyClass.SomeNestedClass y)
    {
        if (x == null || y == null) return ReferenceEquals(x, y);
        return x.decimalField1 == y.decimalField1 && x.intField2 == y.intField2;
    }

    public int GetHashCode(MyClass.SomeNestedClass obj)
    {
        return obj.decimalField1.GetHashCode() * 17 + 
               obj.intField2.GetHashCode();
    }
}

And now we tell Contains to use this class when trying to find a match for our expectedItem:

Assert.IsTrue(results.Contains(expectedItem, new SomeNestedClassComparer()));
Rufus L
  • 36,127
  • 5
  • 30
  • 43