Assert.ThrowsException for async lambdas

UPDATE:  Jake Ginnivan pointed out an inefficiency in my code below.  I was awaiting the async method under test twice.

 

As I mentioned a couple blog posts ago, I want to write tests that provide reasonable examples for users of my libraries. I expect developers to copy my tests as example code and modify it for production code.

That can be tough for async tests when the async method should throw an exception. Well, as I was busy laying the background, Phil Haack leaped ahead to the punchline in his recent blog post.

My own solution was a bit different:

public static async Task ThrowsExceptionAsync<TException>(Func<Task> func)
    where TException : Exception
{
    try
    {
        await func().ConfigureAwait(false);
} catch (TException)
{
        return;
}
    Assert.Fail(“Delegate did not throw “ + typeof(TException).Name);
}

And you’d use it like this:

await ThrowsExceptionAsync<ArgumentException>(() => worker.GetMessage(“   “));

Note that the worker.GetMessage() does not need to be awaited here, because it is awaited in the ThrowsExceptionAsync method.

Most of the differences between my version and Phil’s are due to the fact that I didn’t use any xUnit specific features. However, one part I will discuss in a bit more detail.

Note the ConfigureAwait(false) extension method when calling the async method under test: That instructs the framework to avoid the extra work of marshaling the continuation (the code after the await) onto the calling thread. This is a good practice for library code that does not need to have its continuations execute on the UI thread. I’ll discuss this in more detail in a future blog post.

There’s also a few big picture concepts to get across on this post. The first key practice is that that async methods should return Task, not be void methods. If your async methods are void returning, you can’t determine if your async methods worked correctly, or failed. By returning Task, you can query that Task (or let the compiler generate that code) to determine if it succeeded, failed, or was cancelled.

The second key is that you should await for the result of any async method that returns a Task or Task<T> (even in a lambda). That causes the compiler to generate all the code to check that returned Task and ensure that it completed successfully. If it completed in a faulted state, the compiler generates code to throw the exception during the continuation.

Finally, don’t Wait() for Tasks to finish. await tasks. The compiler generates much better code to handle continuations when you await. And, that code is asyncrhonous, not synchronous.

3 comments to Assert.ThrowsException for async lambdas

  • You don’t need the async/await keywords inside the lambda.

    await ThrowsExceptionAsync(async () => await worker.GetMessage(” “));
    can be
    await ThrowsExceptionAsync(() => worker.GetMessage(” “));

    All you are doing by using the async keyword in the lambda is causing a state machine to be generated, when worker.GetMessage() is awaited, it throws, the state machine catches that exception then completes the state machine’s Task with the thrown exception causing the awaited func() to then rethrow the exception.
    The additional async/await is not needed as far as I know.

    A Task represents an asynchronous operation and can be passed through a method, or a lambda in this case.

  • Bill Wagner

    @Jake, thanks for the note. I’ve updated the code and the explanation above.

  • Did you notice the async modifier in buttonAsync_Click method signature? This is crucial, because async modifier tells to compiler that this method contains code that will run asynchronously using await keyword.