I'm writing some UnitTests for a parser and I'm stuck at comparing two List<T>
where T
is a class of my own, that contains another List<S>
.
My UnitTest compares two lists and fails. The code in the UnitTest looks like this:
CollectionAssert.AreEqual(list1, list2, "failed");
I've written a test scenario that should clarify my question:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ComparerTest
{
class Program
{
static void Main(string[] args)
{
List<SimplifiedClass> persons = new List<SimplifiedClass>()
{
new SimplifiedClass()
{
FooBar = "Foo1",
Persons = new List<Person>()
{
new Person(){ ValueA = "Hello", ValueB="Hello"},
new Person(){ ValueA = "Hello2", ValueB="Hello2"},
}
}
};
List<SimplifiedClass> otherPersons = new List<SimplifiedClass>()
{
new SimplifiedClass()
{
FooBar = "Foo1",
Persons = new List<Person>()
{
new Person(){ ValueA = "Hello2", ValueB="Hello2"},
new Person(){ ValueA = "Hello", ValueB="Hello"},
}
}
};
// The goal is to ignore the order of both lists and their sub-lists.. just check if both lists contain the exact items (in the same amount). Basically ignore the order
// This is how I try to compare in my UnitTest:
//CollectionAssert.AreEqual(persons, otherPersons, "failed");
}
}
public class SimplifiedClass
{
public String FooBar { get; set; }
public List<Person> Persons { get; set; }
public override bool Equals(object obj)
{
if (obj == null) { return false;}
PersonComparer personComparer = new PersonComparer();
SimplifiedClass obj2 = (SimplifiedClass)obj;
return this.FooBar == obj2.FooBar && Enumerable.SequenceEqual(this.Persons, obj2.Persons, personComparer); // I think here is my problem
}
public override int GetHashCode()
{
return this.FooBar.GetHashCode() * 117 + this.Persons.GetHashCode();
}
}
public class Person
{
public String ValueA { get; set; }
public String ValueB { get; set; }
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
Person obj2 = (Person)obj;
return this.ValueA == obj2.ValueA && this.ValueB == obj2.ValueB;
}
public override int GetHashCode()
{
if (!String.IsNullOrEmpty(this.ValueA))
{
//return this.ValueA.GetHashCode() ^ this.ValueB.GetHashCode();
return this.ValueA.GetHashCode() * 117 + this.ValueB.GetHashCode();
}
else
{
return this.ValueB.GetHashCode();
}
}
}
public class PersonComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
if (x != null)
{
return x.Equals(y);
}
else
{
return y == null;
}
}
public int GetHashCode(Person obj)
{
return obj.GetHashCode();
}
}
}
The question is strongly related to C# Compare Lists with custom object but ignore order, but I can't find the difference, other than I wrap a list into another object and use the UnitTest one level above.
I've tried to use an IEqualityComparer
:
public class PersonComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
if (x != null)
{
return x.Equals(y);
}
else
{
return y == null;
}
}
public int GetHashCode(Person obj)
{
return obj.GetHashCode();
}
}
Afterwards I've tried to implement the ''IComparable'' interface thats allows the objects to be ordered. (Basically like this: https://stackoverflow.com/a/4188041/225808) However, I don't think my object can be brought into a natural order. Therefore I consider this a hack, if I come up with random ways to sort my class.
public class Person : IComparable<Person>
public int CompareTo(Person other)
{
if (this.GetHashCode() > other.GetHashCode()) return -1;
if (this.GetHashCode() == other.GetHashCode()) return 0;
return 1;
}
I hope I've made no mistakes while simplifying my problem. I think the main problems are:
- How can I allow my custom objects to be comparable and define the equality in SimplifiedClass, that relies on the comparision of subclasses (e.g. Person in a list, like
List<Person>
). I assumeEnumerable.SequenceEqual
should be replaced with something else, but I don't know with what. - Is
CollectionAssert.AreEqual
the correct method in my UnitTest?
Equals
on aList<T>
will only check reference equality between the lists themselves, it does not attempt to look at the items in the list. And as you said you don't want to useSequenceEqual
because you don't care about the ordering. In that case you should useCollectionAssert.AreEquivalent
, it acts just likeEnumerable.SequenceEqual
however it does not care about the order of the two collections.For a more general method that can be used in code it will be a little more complicated, here is a re-implemented version of what Microsoft is doing in their assert method.
What it does is it makes a dictionary out of the two collections, counting the duplicates and storing it as the value. It then compares the two dictionaries to make sure that the duplicate count matches for both sides. This lets you know that
{1, 2, 2, 3}
and{1, 2, 3, 3}
are not equal whereEnumerable.Execpt
would tell you that they where.