How does Assert.AreEqual determine equality between two generic IEnumerables?
Solution 1
Assert.AreEqual
is going to compare the two objects at hand. IEnumerable
s are types in and of themselves, and provide a mechanism to iterate over some collection...but they are not actually that collection. Your original comparison compared two IEnumerable
s, which is a valid comparison...but not what you needed. You needed to compare what the two IEnumerable
s were intended to enumerate.
Here is how I compare two enumerables:
Assert.AreEqual(t1.Count(), t2.Count());
IEnumerator<Token> e1 = t1.GetEnumerator();
IEnumerator<Token> e2 = t2.GetEnumerator();
while (e1.MoveNext() && e2.MoveNext())
{
Assert.AreEqual(e1.Current, e2.Current);
}
I am not sure whether the above is less code than your .Zip
method, but it is about as simple as it gets.
Solution 2
Found it:
Assert.IsTrue(expected.SequenceEqual(actual));
Solution 3
Have you considered using the CollectionAssert
class instead...considering that it is intended to perform equality checks on collections?
Addendum:
If the 'collections' being compared are enumerations, then simply wrapping them with 'new List<T>(enumeration)
' is the easiest way to perform the comparison. Constructing a new list causes some overhead of course, but in the context of a unit test this should not matter too much I hope?
Solution 4
I think the simplest and clearest way to assert the equality you want is a combination of the answer by jerryjvl and comment on his post by MEMark - combine CollectionAssert.AreEqual
with extension methods:
CollectionAssert.AreEqual(expected.ToArray(), actual.ToArray());
This gives richer error information than the SequenceEqual answer suggested by the OP (it will tell you which element was found that was unexpected). For example:
IEnumerable<string> expected = new List<string> { "a", "b" };
IEnumerable<string> actual = new List<string> { "a", "c" }; // mismatching second element
CollectionAssert.AreEqual(expected.ToArray(), actual.ToArray());
// Helpful failure message!
// CollectionAssert.AreEqual failed. (Element at index 1 do not match.)
Assert.IsTrue(expected.SequenceEqual(actual));
// Mediocre failure message:
// Assert.IsTrue failed.
You'll be really pleased you did it this way if/when your test fails - sometimes you can even know what's wrong without having to break out the debugger - and hey you're doing TDD right, so you write a failing test first, right? ;-)
The error messages get even more helpful if you're using AreEquivalent
to test for equivalence (order doesn't matter):
CollectionAssert.AreEquivalent(expected.ToList(), actual.ToList());
// really helpful error message!
// CollectionAssert.AreEquivalent failed. The expected collection contains 1
// occurrence(s) of <b>. The actual collection contains 0 occurrence(s).
Comments
-
Jason Baker over 3 years
I have a unit test to check whether a method returns the correct
IEnumerable
. The method builds the enumerable usingyield return
. The class that it is an enumerable of is below:enum TokenType { NUMBER, COMMAND, ARITHMETIC, } internal class Token { public TokenType type { get; set; } public string text { get; set; } public static bool operator == (Token lh, Token rh) { return (lh.type == rh.type) && (lh.text == rh.text); } public static bool operator != (Token lh, Token rh) { return !(lh == rh); } public override int GetHashCode() { return text.GetHashCode() % type.GetHashCode(); } public override bool Equals(object obj) { return this == (Token)obj; } }
This is the relevant part of the method:
foreach (var lookup in REGEX_MAPPING) { if (lookup.re.IsMatch(s)) { yield return new Token { type = lookup.type, text = s }; break; } }
If I store the result of this method in
actual
, make another enumerableexpected
, and compare them like this...Assert.AreEqual(expected, actual);
..., the assertion fails.
I wrote an extension method for
IEnumerable
that is similar to Python'szip
function (it combines two IEnumerables into a set of pairs) and tried this:foreach(Token[] t in expected.zip(actual)) { Assert.AreEqual(t[0], t[1]); }
It worked! So what is the difference between these two
Assert.AreEqual
s? -
Jason Baker almost 15 yearsThis makes sense. Is there not any way to compare the two IEnumerables object by object without having to write as much code as it took?
-
jrista almost 15 yearsI would just use a while loop. I've updated my answer with an example.
-
Jason Baker almost 15 yearsI found another way that works and is simpler. I'll accept this since you showed why my code wasn't working though. :-)
-
jrista almost 15 yearsNice find! I didn't even know there was a SequenceEqual() extension. Ty!
-
Jason Baker almost 15 yearsAll the methods in CollectionAssert are set to compare against ICollections though. I have IEnumerables. Is there any easy way to change them to ICollections?
-
jerryjvl almost 15 yearsIn cases like that I normally just do a quick wrap-up in a
new List<T>(enumeration)
so that I can perform the comparison. It's not like the overhead is likely to be a problem in the context of a unit test. -
Marc Gravell almost 15 yearsSome pointers: that approach involves checking both sequences twice (once for count, once per item) - not very efficient. Plus, IEnumerator<T> is IDisposable, so should involve "using". Finally, you can use EqualityComparer<T>.Default.Equals(e1.Current,e2.Current) to avoid boxing etc.
-
jrista almost 15 yearsYes, it does iterate twice...but it is a simple implementation. ;) There is a more complex version that gets the job done more efficiently, but with less clarity. If your comparing two very large enumerations, a more efficient method would be required, but I like the clarity of mine. (However, using statements should indeed be used...left out for brevity.)
-
MEMark over 12 yearsJust do .ToArray() on both arguments when using CollectionAssert.
-
Joy George Kunjikkuru over 12 yearsThanks very much.You saved my loops ;-)
-
bacar almost 12 yearsThis won't tell you any information about which element was unequal when the test fails; it will just tell you that it failed. The CollectionAssert mechanism suggested by jerryjvl gives much richer failure information.
-
Chris over 11 yearsBe sure to include System.Linq in your using directives.