36

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?

J0e3gan
  • 8,740
  • 10
  • 53
  • 80
Jason Baker
  • 192,085
  • 135
  • 376
  • 510
  • 1
    @Jason Baker: please don't take this the wrong way, have you considered that the fact you have to ask a question like this, might mean you are making things too complicated? – Mitch Wheat Jun 01 '09 at 01:40
  • 2
    Erm... no, not really. :-) Could you point me towards where I'm making things complicated? – Jason Baker Jun 01 '09 at 01:42
  • Also, I'm not convinced that using "text.GetHashCode() % type.GetHashCode();" as a return value for GetHashCode() is a good idea... – Mitch Wheat Jun 01 '09 at 01:43
  • 2
    @Mitch Wheat - I'm not sure either... but that's the subject of a different question. – Jason Baker Jun 01 '09 at 01:46

4 Answers4

89

Found it:

Assert.IsTrue(expected.SequenceEqual(actual));
Jason Baker
  • 192,085
  • 135
  • 376
  • 510
  • Nice find! I didn't even know there was a SequenceEqual() extension. Ty! – jrista Jun 01 '09 at 01:57
  • 3
    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. – bacar Jun 18 '12 at 18:56
  • Be sure to include System.Linq in your using directives. – Chris Dec 12 '12 at 12:53
49

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?

J0e3gan
  • 8,740
  • 10
  • 53
  • 80
jerryjvl
  • 19,723
  • 7
  • 40
  • 55
  • 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? – Jason Baker Jun 01 '09 at 02:13
  • In cases like that I normally just do a quick wrap-up in a `new List(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. – jerryjvl Jun 01 '09 at 02:20
  • 6
    Just do .ToArray() on both arguments when using CollectionAssert. – MEMark Oct 31 '11 at 19:18
25

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.

J0e3gan
  • 8,740
  • 10
  • 53
  • 80
jrista
  • 32,447
  • 15
  • 90
  • 130
  • 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? – Jason Baker Jun 01 '09 at 01:48
  • I would just use a while loop. I've updated my answer with an example. – jrista Jun 01 '09 at 01:50
  • I found another way that works and is simpler. I'll accept this since you showed why my code wasn't working though. :-) – Jason Baker Jun 01 '09 at 01:55
  • 5
    Some pointers: that approach involves checking both sequences twice (once for count, once per item) - not very efficient. Plus, IEnumerator is IDisposable, so should involve "using". Finally, you can use EqualityComparer.Default.Equals(e1.Current,e2.Current) to avoid boxing etc. – Marc Gravell Jun 01 '09 at 04:28
  • 2
    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.) – jrista Jun 01 '09 at 05:16
21

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).   
bacar
  • 9,761
  • 11
  • 55
  • 75