Unit testing exceptions and the await keyword

Hackered
Tuesday, April 8, 2014
by Sean McAlinden

The new await and async keywords have been a great addition to the c# language, anything that helps abstract away the complexities of threading can only be a good thing. Unit testing thread heavy applications that utilise await and async can be slightly more problematic but there are a few tricks that can make life a little easier.   In this post I'll show a way to assert an exception is thrown by an async SUT (system under test).   It is worth noting that there are many ways of achieving test coverage for this scenario, this is just an approach that I don't completely dislike.  

The async SUT throws an exception

Lets say you have some code that throws an exception if a parameter is null:

public class MyService
{
    public async Task DoSomething(string name)
    {
        if (name == null)
        {
            throw new ArgumentNullException();
        }
    }
}

In the good old days we could call Assert.Throws<ArgumentNullException>(() => myService.DoSomething(null)); and move on to the next test. Unfortunately, because the method returns a task, the normal Assert.Throws syntax is not going to work, many people have unfortunately lost countless hours discovering this for themselves.   Here I normally use a hand cranked ThrowsAsync method similar to the following (I am using the Should library for the assertions, this is available via nuget):

public static class AsyncTestHelper
{
    public static async Task<Exception> ThrowsAsync<TException>(Func<Task> function) where TException : Exception
    {
        var exceptionThrown = false;

        try
        {
            await function();
        }
        catch (TException exception)
        {
            typeof(TException).ShouldEqual(typeof(TException));
            return exception;
        }
        catch (Exception ex)
        {
            exceptionThrown = true;
            ex.GetType().ShouldEqual(typeof(TException));
        }

        exceptionThrown.ShouldBeTrue("No exception was thrown");
        return null;
    }
}
  • The function is called within the try block.
  • If the correct exception is thrown within the code, the first catch block performs an assert that will always equal true (this is for the test runner to pass if this was the only assertion).
  • The exception is returned so more assertions can be tried against the thrown exception such as the message string.
  • If the incorrect exception is thrown, an equality assertion is performed which will fail and print out the actual and expected exception types.
  • If no exception is thrown, a true assertion which always fails is performed with a message stating no exception was thrown.

 

How is it called?

[Test]
public async Task Test()
{
    var myService = new MyService();
    var exception = await AsyncTestHelper.ThrowsAsync<ArgumentNullException>(() => myService.DoSomething(null));
    exception.Message.ShouldEqual("Value cannot be null.");
}

Have a play with it and see what you think, it works well and hopefully something similar will make it into one of the core libraries at some point (unless it already has and I haven't noticed).