Latest Tweet:
  • Loading...

One of the new features introduced in .NET 2.0 is the Event-based Asynchronous Pattern.  A class that supports this will have one or more methods named MethodNameAsync. These methods often mirror synchronous versions, which perform the same operation on the current thread. The class may also have a MethodNameCompleted event and it may have a MethodNameAsyncCancel method.

The Event-Based Asynchronous Pattern is used several places in the .NET 2.0 Framework , and one example many might find familiar are the auto generated Web Service Proxy classes. For every web service method you get a async version, and an event fired when the async method call finishes. The data is returned as the event argument.  Code like this is really nice to work with, and makes it really simple to make more responsive clients where all web service calls happens on a separate thread. The disadvantage with asynchronous code is that it's harder to write unit tests for.

This is a problem I've faced many times, and I've seen several different solutions. Many of them involves sleeping the thread executing the test, while waiting for the data to come back so that you can do any assertions on the returned data. This has several disadvantages. You don't now exactly how long the asynchronous call is going to take, so you need to sleep "long enough". Having thread sleeps in your tests are going to (big surprise) make your test suite slow. Now matter how fast your service returns it data, you still have to wait for all thread sleeps before the test ends. This is really inefficient, and can really become a problem when you have a large test suite integrated into a build environment. Another problem is that this is an unreliable way to do your testing, as they might pass some times, while fail others (if the thread sleep isn't long enough).

The best solution I've found to unit testing of asynchronous code is the ManualResetEvent class found in the System.Threading namespace. I'm no threading expert, and I'm not going to try to explain how the .NET threading model works, but I've included a little snippet from the MSDN documentation explaining what you can use the ManualResetEvent class for:

"ManualResetEvent allows threads to communicate with each other by signaling. Typically, this communication concerns a task which one thread must complete before other threads can proceed".

This is exactly what we want. When the thread executing the unit test starts an asynchronous call we want to block the thread, and wait for a signal from the callback before we continue. This way we spend the minimal time needed waiting for a result from the async method call, and don't have to depend on thread sleeping for synchronizing the test with the result from the async method. I've included a simple NUnit test for a web service offering basic arithmetic operations. It calls the async versions of the add and subtract methods, performs assertions both on the returned data, and check if the async call actually completed. The details on how to use the ManualResetEvent is included in the code sample beneath (check the comments).

[Test]
public void TestCalculations()
{
    // A simple web service offering basic aritmetich operations
    Service service = new Service();

    // Test data...
    int a = 10;
    int b = 10;

    // Variables to track is a asyn call completed.
    bool addCompleted = false;
    bool substractCompleted = false;

    // Used to signal the waiting test thread that a async operation have completed.
    ManualResetEvent manualEvent = new ManualResetEvent(false);

    // Async callback events are anonomous and are in the same scope as the test code,
    // and therefore have access to the manualEvent variable.
    service.AddCompleted += delegate(object sender, AddCompletedEventArgs args)
    {
        // Some basic assertions on the code.
        Assert.AreEqual(a + b, args.Result);
        addCompleted = true;

        // Signal the waiting NUnit thread that we're ready to move on.
        manualEvent.Set();
    };
    
    service.SubstractCompleted += delegate(object sender, SubstractCompletedEventArgs args)
    {
        Assert.AreEqual(a - b, args.Result);
        substractCompleted = true;
        manualEvent.Set();
    };

    // Call the add method asyncronous.
    service.AddAsync(a, b);

    // Block the current thread untill the callback event signals
    // or the call times out (1500 ms).
    manualEvent.WaitOne(1500, false);

    // Check if we completed the async call, or fail the test if we timed out.
    Assert.IsTrue(addCompleted);

    // Set the event to non-signaled before making next async call.
    manualEvent.Reset();

    service.SubstractAsync(a, b);
    manualEvent.WaitOne(1500, false);
    Assert.IsTrue(substractCompleted);
}
<August 2010>
SunMonTueWedThuFriSat
25262728293031
1234567
891011121314
15161718192021
22232425262728
2930311234