0

I had a problem with using .NET collections with Nsubstitue objects.

  1. I have a base class where I implement Equals(object), CompareTo function
  2. In the test I create two exact Nsubstitue object proxy for this base class.
  3. After I put the object in the collection, the collection shows that these two object proxy are two different objects.

I wonder what could be the reason of this behavior, and how to define the a collection with mockups.

public class KeyTestClass : IKeyTestClass
{
    public int Id { get; private set; } 

    public KeyTestClass()
    {
        Id = 1;
    }

    public override int GetHashCode()
    {
        return Id;
    }


    public int CompareTo(IKeyTestClass other)
    {
        return Id - other.Id;
    }

    public bool Equals(IKeyTestClass other)
    {
        return Id == other.Id;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != GetType()) return false;
        return Equals((KeyTestClass)obj);
    }


}

public interface IKeyTestClass : IComparable<IKeyTestClass>, IEquatable<IKeyTestClass>
{
    int Id { get; }
}

public class KeyTestClass2 : IKeyTestClass2
{

}

public interface IKeyTestClass2
{

} 

[TestClass]
public class ConsistencyRelatedTests
{

    [TestMethod]
    public void ValidateTestClass()
    {

        var dic = new Dictionary<IKeyTestClass,List<IKeyTestClass2>>();

        // using new equals function defined by Nsubstittue.
        var item1 = Substitute.For<IKeyTestClass>();
        var item2 = Substitute.For<IKeyTestClass>();
        item1.Id.Returns(1);
        item2.Id.Returns(1);

        Assert.IsTrue(item1.Equals(item2)); //false, not working
        dic.Add(item1, new List<IKeyTestClass2>());
        dic.Add(item2, new List<IKeyTestClass2>());

        // Using real class equals method
        var item3 = new KeyTestClass();
        var item4 = new KeyTestClass();
        Assert.IsTrue(item3.Equals(item4)); //working
        dic.Add(item3, new List<IKeyTestClass2>());
        dic.Add(item4, new List<IKeyTestClass2>());
    }

}
AlG
  • 14,697
  • 4
  • 41
  • 54
books
  • 3
  • 1
  • My gut feeling is that since there is no setup for the **Equals(IKeyTestClass other)** method on the mocks (item1 and item2), it will return the default value for the method, which in this case is false. – Dimitar Tsonev Jun 28 '17 at 21:12

1 Answers1

2

In this example Equals() is implemented for KeyTestClass. That does not mean it applies to all instances of the IKeyTestClass. If we create another class KeyTestClass2 : IKeyTestClass and use default Equals we'd get exactly the same result:

var item1 = new KeyTestClass2();
var item2 = new KeyTestClass2();
Assert.IsTrue(item1.Equals(item2)); // also fails, as expected

This is pretty much what NSubstitute does when we call Substitute.For<IKeyTestClass>(). It creates a new class implementing the interface, so it is completely unaware of the KeyTestClass.Equals implementation.

There are a few options here depending on what you are trying to do. Firstly, your KeyTestClass.Equals works with all IKeyTestClass instances, including substitutes, so you can use that fine:

var item1 = new KeyTestClass(); 
var item2 = Substitute.For<IKeyTestClass>();
var item3 = Substitute.For<IKeyTestClass>();
item2.Id.Returns(1);
item3.Id.Returns(42);
Assert.IsTrue(item1.Equals(item2)); // Passes (as expected)
Assert.IsFalse(item1.Equals(item3)); // Passes (as expected)

Secondly, you could substitute the IEquatable<IKeyTestClass>.Equals method on the substitutes (NOTE: not for Object.Equals, that can cause problems).

var item1 = Substitute.For<IKeyTestClass>();
var item2 = Substitute.For<IKeyTestClass>();
item1.Id.Returns(1);
item2.Id.Returns(1);
item1.Equals(item2).Returns(true);

Assert.IsTrue(item1.Equals(item2)); // Passes as expected

In this case you may want to also stub out item2.Equals(item1). We can also delegate to the real logic with item1.Equals(Arg.Any<IKeyTestClass>).Returns(x => KeyTestClass.Equals(item1, x.Arg<IKeyTestClass>()) (requires a static version of Equals logic extracted), and configure each substitute with this call.

Another option to consider is to use the real KeyTestClass here (or a hand-coded test double) if at all possible. Equality can be a bit tricky in .NET due to the fallback to Object.Equals for everything, so a bit of care is needed stubbing this out when equality is a key part of the contract.

David Tchepak
  • 9,826
  • 2
  • 56
  • 68