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
}
2
  • 1
    What about dupes. Is {2,2,1} a match against {1,2}? Commented Sep 5, 2013 at 9:09
  • @spender, not a match (but this won't matter now because we don't have any duplicates. Commented Sep 5, 2013 at 9:20

1 Answer 1

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.)

Sign up to request clarification or add additional context in comments.

5 Comments

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 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).
What is the reason that Assert.AreEqual does not function correctly on the AppointmentCollection? Is my implementation of GetHashCode incorrect then?
@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).
If I use 'Assert.IsTrue(list1.Equals(list2));' it succeeds, but if I use 'Assert.AreEqual(list1, list2);' it fails.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.