How does Assert.AreEqual determine equality between two generic IEnumerables?

33,220

Solution 1

Assert.AreEqual is going to compare the two objects at hand. IEnumerables 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 IEnumerables, which is a valid comparison...but not what you needed. You needed to compare what the two IEnumerables 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).   
Share:
33,220
Jason Baker
Author by

Jason Baker

I'm a developer on Google's Cloud Console.

Updated on November 07, 2020

Comments

  • Jason Baker
    Jason Baker over 3 years

    I have a unit test to check whether a method returns the correct IEnumerable. The method builds the enumerable using yield 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 enumerable expected, and compare them like this...

      Assert.AreEqual(expected, actual);
    

    ..., the assertion fails.

    I wrote an extension method for IEnumerable that is similar to Python's zip 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.AreEquals?

  • Jason Baker
    Jason Baker almost 15 years
    This 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
    jrista almost 15 years
    I would just use a while loop. I've updated my answer with an example.
  • Jason Baker
    Jason Baker almost 15 years
    I found another way that works and is simpler. I'll accept this since you showed why my code wasn't working though. :-)
  • jrista
    jrista almost 15 years
    Nice find! I didn't even know there was a SequenceEqual() extension. Ty!
  • Jason Baker
    Jason Baker almost 15 years
    All 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
    jerryjvl almost 15 years
    In 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
    Marc Gravell almost 15 years
    Some 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
    jrista almost 15 years
    Yes, 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
    MEMark over 12 years
    Just do .ToArray() on both arguments when using CollectionAssert.
  • Joy George Kunjikkuru
    Joy George Kunjikkuru over 12 years
    Thanks very much.You saved my loops ;-)
  • bacar
    bacar almost 12 years
    This 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
    Chris over 11 years
    Be sure to include System.Linq in your using directives.