Assert.AreEqual in MSTest – done right

Every unit testing framework out there comes with a plethora of assertion methods – this is not necessarily a good thing.
Instead of writing simple code a developer is forced to choose the correct assertion method from a seemingly endless list of methods, most of which look exactly the same! Having multiple overloaded methods with the same name does not help at all, and sometimes the sheer amount of methods conceals the fact that the Assert you really need – does not exist at all.

One of those so-called missing assertions is the one that enables a developer to compare two objects of the same type – by default most unit testing frameworks will use Equals method that in C#/Java will only check that the two instances point to the same reference.

Consider the following class:

public class MyClass
{
    public int MyInt { get; }
    public string MyString { get; }

    public MyClass(int myInt, string myString)
    {
        MyInt = myInt;
        MyString = myString;
    }

    public override string ToString()
    {
        return $"{nameof(MyInt)}: {MyInt}, {nameof(MyString)}: {MyString}";
    }
}

If we try to write the following test:

[TestMethod]
public void CheckForEquality()
{
    var myClass = new ClassUnderTest();
    var result = myClass.SomeMethodINeedToCheck();

    var expected = new MyClass(1, "some string");

    Assert.AreEqual(expected, result);
}

That test would always fail:

Failure

What I tend to recommend in this case is to override MyClass.Equals so that it would check the actual values instead of just a reference. Unfortunately this solution leaves much to be desired – what happens if I want to compare different values in different tests? not to mention the fact that in some cases updating a class in your production code is not possible or desired. Another option is to use a 3rd party assertion libraries such as FluentAssertions or Shouldly – but I wish there was a simpler way…

My solution

What I would have liked is for Assert.AreEqual to let me change the way equality is checked.

Some frameworks enable developers to pass a comparer – a class that determines how another class will be tested for equality. But before you start bashing Microsoft know this: MSTest enables developers to pass a Comparer class as part of an assertion call – unfortunately that assertion is part of CollectionAssert which means that it could only be used when comparing two collections – or does it?

Using the magic of Extension methods I wrote the following method to compare two instances of MyClass:

public static void AreEqual<T>(this Assert assert, T expected, T actual, IComparer comparer)
{
    CollectionAssert.AreEqual(
        new[] { expected },
        new[] { actual }, comparer,
        $"\nExpected: <{expected}>.\nActual: <{actual}>.");
}

The reason I needed to add the third parameter (error description) is due to the fact that MSTest error would only tell us that index 0 does not equal and we want to see an error message that looks like this:

failure2.JPG

The test itself is quite trivial:

[TestMethod]
public void CheckForEquality()
{
    var myClass = new ClassUnderTest();
    var result = myClass.SomeMethodINeedToCheck();

    var expected = new MyClass(1, "some string1");

    Assert.That.AreEqual(expected, result, new MyClassComparer());
}

Notice the “Assert.That” in the last line? This method was created as an extensibility point – this is where we can hook Extensions methods just like the one we’re using here.

Improving with Lambdas

The solution above solved the problem of comparing two instances using one assertion, which is nice. Unfortunately writing a new comparer each time is both repetitive (check that values of the same type, then check if one of them is not null), and error-prone (you should have checked for null before casting) and so I would have prefered to write the boilerplate code once and then only change the actual values that matters – the actual comparison.

And so I’ve written the following Comparer:

public delegate bool CompareFunc<in T>(T obj1, T obj2);

class LambdaComparer<T> : IComparer
{
    private readonly CompareFunc<T> _compareFunc;

    public LambdaComparer(CompareFunc<T> compareFunc)
    {
        _compareFunc = compareFunc;
    }

    public int Compare(object x, object y)
    {
        if (x == null && y == null)
        {
            return 0;
        }

        if (!(x is T t1) || !(y is T t2))
        {
            return -1;
        }

        return _compareFunc(t1, t2) ? 0 : 1;
    }
}

The class above enable just passing the “compare method” which makes simple tests even simpler – but there’s more, by updating the extension method we can make the Comparer disappear:

public static void AreEqual<T>(this Assert assert, T expected, T actual, CompareFunc<T> compareFunc)
{
    var comparer = new LambdaComparer<T>(compareFunc);

    CollectionAssert.AreEqual(
        new[] { expected },
        new[] { actual }, comparer,
        $"\nExpected: <{expected}>.\nActual: <{actual}>.");
}

And now we can write the following test:

[TestMethod]
public void CheckForEqualityLambda()
{
    var myClass = new ClassUnderTest();
    var result = myClass.SomeMethodINeedToCheck();

    var expected = new MyClass(1, "some string1");

    Assert.That.AreEqual(expected, result,
        (myClass1, myClass2) =>
        myClass1.MyInt == myClass2.MyInt && myClass1.MyString == myClass2.MyString);
}

So there you have it – by using extension methods and the existing capabilities of MSTest we can have a simple and powerful assert and now instead of writing multiple assertions for a single result we can harness the methods above to test that the result is in fact equal to the expected value.

10 thoughts on “Assert.AreEqual in MSTest – done right

  1. I’m getting the following error: ‘Assert’ static types cannot be used as parameters. I’ve added it in a test class. What am I doing wrong?

      1. It’s not the test adapter, it’s the test framework – what version of Visual Studio are you using

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s