My two cents on unit testing asynchronous code

Understand how asynchrony works in .Net and adhere to the recommended practices when writing unit tests to test asynchronous code

unittestingasynccode
Joydip Kanjilal

A proper and planned approach toward unit testing helps you to detect errors in your application and ensures that the application is error free. As such, unit testing has become a fundamental skill that a developer should have.

Essentially, unit testing is used to test blocks or units of code to check if it conforms to the desired results. Unit testing asynchronous code is a bit tricky -- you should be extra careful when unit testing asynchronous code in your application.

Unit testing asynchronous code

Unit testing helps to detect bugs and reduce time to market. Now, as I said earlier, unit testing asynchronous code has a few challenges that you should be aware of. Consider the following piece of code:

public class IDGTest

{

  public static async Task SampleTestMethodAsync()

  {

    await Task.Delay(100);

   //Some other code

  }

}

Now suppose you write the following unit test method to test the piece of code given above:

[TestMethod]

public void WrongApproachTest()

{

  IDGTest.SampleTestMethodAsync();

}

Unfortunately, this unit test method will always pass irrespective of whether the test fails or not. Let's understand this better -- here's an updated version of the async method we are writing our unit test for:

public static async Task SampleTestMethodAsync()

  {

    await Task.Delay(100);

    throw new Exception("An error occurred.");

  }

Note the usage of the await keyword. The async and await keywords are used to implement asynchrony in applications that leverage the latest versions of the .Net Framework. The "await" keyword is used to denote a suspension point -- it captures the current SynchronizationContext and as soon as the task that has been awaited using the "await" keyword is complete, the state machine is resumed and execution of the code in the caller method restarts.

If you run the unit test method again, it will still pass. Why? The asynchronous method under test returns a Task object. It should be noted that when you are unit testing an asynchronous method, you should observe the Task object it returns. To do this, you should await the Task object being returned from the SampleTestMethodAsync method. Here's the updated unit test method that takes advantage of await to call the asynchronous method:

[TestMethod]

public void WrongApproachTest()

{

  await IDGTest.SampleTestMethodAsync();

}

Another point you should be aware of is avoiding async void unit tests. It is quite difficult to retrieve the result of async void unit tests and hence they should be avoided. Unlike async void unit tests that are quite complicated, you can have async Task unit tests, i.e., unit tests that return a Task instance. Almost all the unit test frameworks (MSTest, NUnit, etc.) provide support for such unit tests.

An async method returns control quickly and usually returns a Task that completes its execution at a future point in time. When an exception occurs inside an asynchronous method that has a return type of Task or Task<T>, the exception details are stored inside the Task instance. Consider the following unit test method:

[TestMethod]

[ExpectedException(typeof(DivideByZeroException))]

  public void DivideNumberTest()

  {

      Task<int> task = IDGSample.Divide(10, 0);

      task.Wait();

  }

The IDGSample class is given below. This is a static class with just one static method that returns a Task instance:

public static class IDGSample

    {

        public static async Task<int> Divide(int x, int y)

        {

            await Task.Delay(100);

            return x / y;

        }

    }

In this example, the Task class would wrap exception into an instance of AggregateException. You should not use Task.Wait and Task.Result the way this example illustrates. To unwrap the exception, we should await the Task.

Here's the updated version of the unit test method -- this time we have specified the async keyword in the method signature of the unit test method:

[TestMethod]

[ExpectedException(typeof(DivideByZeroException))]

public async Task DivideNumberTestAsync()

{

  int x = await IDGSample.Divide(10, 0);           

}

When you execute this unit test, it would provide you the desired results. The await keyword makes the difference.

Copyright © 2017 IDG Communications, Inc.