Wednesday, September 07, 2005 - Posts

Unit Testing Asynchronous Code

When developing service oriented applications, developers must master asynchronous programming. Fortunately, .NET shields us from many of the intricacies of the asynchronous programming model. In a Windows Forms application you can use AsyncCallbacks out of the box, because the calling application is likely to be running when an asynchronous method returns, no matter whether the operation is long running or not. Unit tests are different. Tests are often small, and the tests are reinitialized between every single test within a fixture. As a consequence of this, a test is likely to complete in good time before the callback occurs. The below example doesn’t test the callback, because the callback will occur after the TestCalculator method completes.

[TestFixture]
public class ATestThatDoesntWork
{
      [Test]
      public void TestCalculator()
      {
            Calculator calc = new Calculator();
            SquareDelegate asyncMethodDelegate = new SquareDelegate(calc.Square);
            int valueToSquare = 4;
            IAsyncResult result = asyncMethodDelegate.BeginInvoke(valueToSquare, new AsyncCallback(SquareCallback), asyncMethodDelegate);
      }
      public delegate int SquareDelegate(int value);
      public static void SquareCallback(IAsyncResult result)
      {
            SquareDelegate asyncMethodDelegate = result.AsyncState as SquareDelegate;
            int squaredValue = asyncMethodDelegate.EndInvoke(result);
            Assert.AreEqual(16, squaredValue); 
      }
}

An easy solution to this problem is to insert a Thread.Sleep at the end of the TestCalculator method. A problem with this approach is choosing how long the thread should sleep. If you make the period too short, you run the risk that the asynchronous method takes longer to execute than the thread’s nap, and you end with a test that doesn’t really exercise the code. If you make the period too long, you’ll end up with a protracted test suite, and yet you’ll have no guarantee that the callbacks will get tested. Just imagine the impact of a Thread.Sleep(10000) in every single test within a large enterprise project.

A much more elegant and by far less time-consuming approach is to leverage a WaitHandle to wait for the asynchronous call to complete. The example below (C# 2.0) shows how the previous example can be improved.

[TestFixture]
public class CS20CalculatorTest
{
      [Test]
      public void TestCalculator()
      {
            Calculator calc = new Calculator();
            SquareDelegate asyncMethodDelegate = new SquareDelegate(calc.Square);
            int valueToSquare = 4;
            ManualResetEvent waitHandle = new ManualResetEvent(false);
            IAsyncResult asyncResult = asyncMethodDelegate.BeginInvoke(valueToSquare, new AsyncCallback(
                  delegate(IAsyncResult result)
                  {
                        SquareDelegate localAsyncMethodDelegate = (SquareDelegate)result.AsyncState;
                        int squaredValue = localAsyncMethodDelegate.EndInvoke(result);
                        Assert.AreEqual(16, squaredValue);
                        waitHandle.Set();
                  }), asyncMethodDelegate);
            waitHandle.WaitOne();
      }
      public delegate int SquareDelegate(int value);
}
 

This example uses a ManualResetEvent, which is a class that inherits from WaitHandle, to block the thread executing the TestCalculator method until the handle is signaled. I’ve used an anonymous method, which is one of the many new features in C# 2.0, to implement the AsyncCallback delegate. This makes keeps all the test code within the test method, as opposed to the previous example where it is spilt into two methods. The anonymous method has access to the TestCalculator’s local variables. This enables me to declare and instantiate the ManualResetEvent within the test, and signal the handle from within the anonymous method. The ManualResetEvent.Set method gives the signal, and the ManualResetEvent.WaitOne method blocks the thread until the handle has received one signal. This enables the test to proceed as soon as the callback is completed, but not before. Although there are huge benefits with this approach, it has one weakness. If for some reason the asynchronous method never calls back, you run the risk of the test waiting forever. Eddie Garmon has developed a NUnit extension that enables you to set a timeout for tests. This is an excellent tool to guard against infinite tests, just remember to make the timeout period long enough for the asynchronous call to complete.