2

I'm trying to compare 2 Lists (wrapped in an object) containing custom objects. I don't care about the order, but if list 1 contains "1,2,3,4" then list 2 must and only contain those elements. E.g.: "4,2,3,1"

Based on Compare two List<T> objects for equality, ignoring order ignoring-order I've used the Except and Any but it doesn't give me the desired results.

If I use Assert.Equals it fails, but Assert.IsTry(list1.equals(list2)) succeeds.

Further more if I remove the Equals and GetHashCode implementation then both tests fail.

public class AppointmentCollection : List<Appointment>
{
    public override bool Equals(object obj)
    {            
        var appCol = obj as AppointmentCollection;

        if (appCol == null)
        {
            return false;
        }

        return (appCol.Count == this.Count) && !(this.Except(appCol).Any());
    }


    public override int GetHashCode()
    {
        unchecked
        {
            //use 2 primes
            int hash = 17;
            foreach (var appointment in this)
            {
                hash = hash * 19 + appointment.GetHashCode();
            }
            return hash;
        }
    }
}

public class Appointment
{
    public string Title {get; set;}
    public DateTime StartTime {get; set;}
    public DateTime EndTime { get; set;}

    public override bool Equals(object obj)
    {
        var appointment = obj as Appointment;
        if (appointment == null)
        {
            return false;
        }
        return Title.Equals(appointment.Title) &&
            StartTime.Equals(appointment.StartTime) &&
            EndTime.Equals(appointment.EndTime);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            //use 2 primes
            int hash = 17;
            hash = hash * 19 + Title.GetHashCode();
            hash = hash * 19 + StartTime.GetHashCode();
            hash = hash * 19 + EndTime.GetHashCode();
            return hash;
        }
    }
}

[Test]
public void TestAppointmentListComparisonDifferentOrder()
{
    var appointment1 = new Appointment(
        "equals test1",
        new DateTime(2013, 9, 4),
        new DateTime(2013, 9, 4));

    var appointment2 = new Appointment(
        "equals test2",
        new DateTime(2013, 9, 4),
        new DateTime(2013, 9, 4));

    var list1 = new AppointmentCollection() { appointment1, appointment2 };
    var list2 = new AppointmentCollection() { appointment2, appointment1 };

    //With Equals/GetHashCode in AppointmentCollection implemented
    CollectionAssert.AreEqual(list1, list2); //fails
    Assert.IsTrue(list1.Equals(list2)); //success

    //Without Equals/GetHashCode in AppointmentCollection implemented
    CollectionAssert.AreEqual(list1, list2); //fails
    Assert.IsTrue(list1.Equals(list2)); //fails
}
Community
  • 1
  • 1
RvdK
  • 19,580
  • 4
  • 64
  • 107

1 Answers1

7

You didn't state clearly which unit test tool you use. Maybe CollectionAssert is the class Microsoft.VisualStudio.TestTools.UnitTesting.CollectionAssert, or maybe it is NUnit.Framework.CollectionAssert, or maybe something else?

So check the documentation of your testing tool, or write here which one you use.

However, it is common for

CollectionAssert.AreEqual( ... );

to check if the collections are the same in the same order, while

CollectionAssert.AreEquivalent( ... );

will check what you want. So use the latter.

Neither of the two methods on CollectionAssert actually uses your override of Equals(object). To use that, write:

Assert.AreEqual( ... );

Edit: I thought Assert.AreEqual(exp, act); would always end up doing exp.Equals(act) which would call your override on AppointmentCollection. But it turns out we end in the private instance method EqualConstraint.ObjectsEqual, and as one sees it checks if the run-time type implements ICollection in which case your override is never used.

Lesson learned: Using Assert.AreEqual can be confusing with collections. Use CollectionAssert.AreEquivalent or CollectionAssert.AreEqual to make your intention clear. You don't have to override Equals on AppointmentCollection if you only need it for testing. If you need it for the application itself and you want to test that, write the test with list1.Equals(list2) literally to make sure your own override is what is tested.

(In any case the override on Appointment is needed, of course.)

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • Sorry, it was NUnit. I was wondering why AreEquals and IsTry+Equals gave different results. Because if I want to check in code (and not in the Test) I need to know if the comparison is correct. – RvdK Sep 05 '13 at 10:02
  • @RvdK Did you see my updated answer? `Assert.AreEqual` is distinct from `CollectionAssert.AreEqual`. Your question is confusing the two. The first one simply calls `Equals` once on the collections. This is calling you override on `AppointmentCollection`. The second one iterates through the collections and calls `Equals` on the individual `Appointment` instances. This does not use your override **on `AppointmentCollection`** (but does use your override on `Appointment`). – Jeppe Stig Nielsen Sep 05 '13 at 10:10
  • What is the reason that Assert.AreEqual does not function correctly on the AppointmentCollection? Is my implementation of GetHashCode incorrect then? – RvdK Sep 05 '13 at 10:16
  • @RvdK Your code does not check `Assert.AreEqual`. You only use `CollectionAssert.AreEqual`. I would say when `Assert.IsTrue(list1.Equals(list2))` is OK, then so is `Assert.AreEqual(list1, list2)`. – Jeppe Stig Nielsen Sep 05 '13 at 10:30
  • 1
    If I use 'Assert.IsTrue(list1.Equals(list2));' it succeeds, but if I use 'Assert.AreEqual(list1, list2);' it fails. – RvdK Sep 05 '13 at 11:42