jimmy keen

on .NET, C# and unit testing

Comparing lists with NUnit – the definite guide

January 02, 2015 | tags: c# unit-testing nunit list

Very often you’ll run into situation where in your unit test you have to compare two lists against each other. In this post I show several ways to do that, describe common pitfalls and nuances.

Simple types with Assert.AreEqual

When our list contains “simple type”1 like int or string assertion is rather straightforward. There is plethora of different methods we could use but for now NUnit’s Assert.AreEqual should be enough:

[Test]
public void SimpeTypes_Integer()
{
    var expected = new List<int> { 1, 2, 5 };
    var actual = new List<int> { 1, 2, 5 };

    CollectionAssert.AreEqual(expected, actual); // collections specific
    Assert.AreEqual(expected, actual);           // classic model
    Assert.That(actual, Is.EqualTo(expected));   // constraint model
}

NUnit will then use its custom EnumerablesEqual to verify whether each element of actual has its corresponding element in expected (note that both lists have to be in the same order). Comparison of individual elements will default to Equals call.

In case order of elements is irrelevant we’ll have to use Equivalent comparison instead:

[Test]
public void SimpleTypes_IntNotInOrder()
{
    var expected = new List<int> { 1, 2, 5 };
    var actual = new List<int> { 1, 5, 2 };

    CollectionAssert.AreEquivalent(expected, actual);
    Assert.That(actual, Is.EquivalentTo(expected));
}

Custom types

Problems arise when we have to compare collections that contain our custom-defined types. Consider this naive Quotation type which stores information about stock closing price:

public class Quotation
{
    public string Ticker { get; set; }
    public DateTime Date { get; set; }
    public decimal Close { get; set; }
}

The following test will naturally fail as we know even though compared quotations are semantically the same, they are in fact, different instances:

[Test]
public void CustomTypes_Quotation()
{
    var expected = CreateQuotations().ToList();
    var actual = CreateQuotations().ToList();

    Assert.AreEqual(expected, actual);
    Assert.That(expected, Is.EqualTo(actual));
}

private IEnumerable<Quotation> CreateQuotations()
{
    var date = new DateTime(2014, 07, 31);
    yield return new Quotation { Ticker = "APPL", Close = 110.38m, Date = date };
    yield return new Quotation { Ticker = "MSFT", Close =  46.45m, Date = date };
}

This is easily fixable though. Remember how I mentioned NUnit will default to using Equals in individual objects comparisons? All we have to do is provide one. Or do we? There are few issues with such approach:

Since there doesn’t seem to be any better way to do this2, for now we’ll implement Equals to make the test pass.

Problem with reference types

What about types we cannot override Equals? Like XElement? We could project our collection to bit more representable form:

[Test]
public void ReferenceTypes_XElement()
{
    var list = new List<XElement> { new XElement("a"), new XElement("b") };
    var expected = list.Select(x => x.ToString()).ToList();
    var actual = list.Select(x => x.ToString()).ToList();

    CollectionAssert.AreEqual(expected, actual);
    Assert.AreEqual(expected, actual);
    Assert.That(expected, Is.EqualTo(actual));
}

This works but you can probably already see problems with such approaches – we have to add methods we don’t really need (Equals), use methods that were not meant to be used this way (ToString) or resort to overly complex solutions. It looks very clumsy. We need to find a better way.

Enter FluentAssertions

Just like virtually any problem you might ever encounter this one has, too, already been solved. FluentAssertions is a library that contains a set of extensions to make asserts more fluent (name kind of spoiled it). On top of really good API and clear error messages it provides solutions for common assertions problems, like the one we are facing here.

The ShouldBeEquivalentTo extension method is what we are looking for. It is a smart method that will compare entire objects graph and apply appropriate strategy depending on underlying types. In our case, the actual XMLs will be compared (or to be more specific, properties of XElement that are relevant to XML semantics). This makes our test look like this:

[Test]
public void ReferenceTypes_XElement()
{
    var expected = new List<XElement> { new XElement("a"), new XElement("b") };
    var actual = new List<XElement> { new XElement("b"), new XElement("a") };

    actual.ShouldBeEquivalentTo(expected);
}

You might have noticed I changed the order of elements in actual and expected lists. By default ShouldBeEquivalentTo performs orderless comparison (same as NUnit’s Is.EquivalentTo). Changing this behavior is as easy as this:

[Test]
public void ReferenceTypes_XElementOrderMatters()
{
    var expected = new List<XElement> { new XElement("a"), new XElement("b") };
    var actual = new List<XElement> { new XElement("a"), new XElement("b") };

    actual.ShouldBeEquivalentTo(
        expected,
        options => options.WithStrictOrdering()
    );
}

In case order is different we’ll get helpful message explaining what’s going on:

Expected item[0].Name.LocalName to be “a”, but “b” differs near “b” (index 0).
Expected item[1].Name.LocalName to be “b”, but “a” differs near “a” (index 0).

We can also get rid of Equals method on our Quotation class. ShouldBeEquivalentTo will compare type’s public properties against each other to a decent depth level – they don’t even have to be the same type (all of this is highly configurable).

ShouldBeEquivalentTo is just one of multitude of methods and approaches you can use. For collections alone there’s 20+ custom methods. Resulting assertions are fluent. I bet you know exactly what’s going on in the following snippet without a word of explanation:

actual
  .Should()
  .HaveCount(2).And
  .OnlyContain(q => q.Date == 31.July(2014)).And
  .ContainSingle(q => q.Ticker == "MSFT");

Notice the 31.July(2014). It is FluentAssertions’s fluent API for date & time.

Conclusion

I barely scratched the surface of FluentAssertions. It supports many different types, has excellent extensibility options, is highly configurable, produces meaningful error messages and very readable unit tests code. Go discover it all yourself. Forget about NUnit’s Assert. Even though it is good API, FluentAssertions is simply vastly superior. Once you’ll start using it you will never go back.

The definite guide contains single rule – start using FluentAssertions.

  1. By “simple type” I mean catch-all phrase for CLR primitive or any other value type or System.String; not simple type as understood by C# spec

  2. We could implement a workaround using Has.All.Matches but this is not the direction we want to go