C# 5.0 Async-Feature: Unit Testing, Part I

CSharp goes async

CSharp goes async

Disclaimer: This post is based on the C# 5.0 CTP. Everything described here is subject to future changes.

I’ve already discussed some implications of the async feature in my previous post.  In this post I focus on some challenges for unit test. Once again I assume that everyone is informed about the basics of the new feature. Otherwise I recommend to visit the official CTP-site which links to videos, blog-posts, white-papers and the binaries to try it yourself.

Let’s examine how the async-stuff affects unit testing. Suppose we write a small desktop application which does some work in the background. We’re going write some tests for it.  First lets start with a simple, classic test without any async operations.

The Test

[Test]
public void ExpectTonsOfMoney(){
    var toTest = new MyBusinessLogic();
    var result = toTest.AwesomeBusinessOperation();
    Assert.AreEqual("Tons of money",result);
}

The Implementation

public string AwesomeBusinessOperation()
{
    var firstPart = TakesALongTimeToProcess("Tons");
    var secondPart = TakesALongTimeToProcess(firstPart+" of ");
    var result = TakesALongTimeToProcess(secondPart+"money");
    return result;
}

private string TakesALongTimeToProcess(string word)
{
    // This operation takes a while
    Thread.Sleep(1000);
    return word;
}

Since calculating the string ‘Tons of money’ takes a long time and leaves our GUI unresponsive we’re going to improve the situation. We’re taking advantage of the new async support. Since this tiny example doesn’t have any real asynchronous operations we just spin off a task which sleeps of a while. In real application you would call other async API’s which do the work. Then we refactor the method ‘AwesomeBusinessOperation’ to return a Task of string and use the await operator in the sub-operation.  We also add the suffix ‘Async’ to the methods to distinguish them from regular operations.

public async Task<string> AwesomeBusinessOperationAsync()
{
    var firstPart = await TakesALongTimeToProcessAsync("Tons");
    var secondPart = await TakesALongTimeToProcessAsync(firstPart + " to ");
    var result = await TakesALongTimeToProcessAsync(secondPart + "money");
    return result;
}

private Task<string> TakesALongTimeToProcessAsync(string word)
{
    // Remember, this is just a simulation
    // Usually you would use some other async API here
    return TaskEx.Run(() =>
                   {
                       // This operation takes a while
                       Thread.Sleep(1000);
                       return word;

                   });
}

Now we change the test. Luckily this is very easy. Instead of using the result directly we just use the Task.Result property. This will block the thread until the calculation is done. This also means we’ve no trouble testing it, we just can wait for the result.  So we can refactor to async operations without making our tests a nightmare.

[Test]
public void ExpectTonsOfMoney(){
    var toTest = new MyBusinessLogic();
    var result = toTest.AwesomeBusinessOperationAsync();
    Assert.AreEqual("Tons of money",result.Result);
}

Testing Side-Effects

Now our example about is quite naïve, isn’t it? I mean we just calculated some result, which is easy to test. Let’s look at a example where we have some side effects. A typical example for a desktop application is to trigger events. Again we start with a simple, synchronous example.

The Test

[Test]
public void ExpectEventToBeFired(){
    var toTest = new MyBusinessLogic();
    var eventFiredExpected = "";
    toTest.OperationFinishedNotification 
        += eventArgument =>
                    {
                        eventFiredExpected = eventArgument;
                    };
    var result = toTest.AwesomeBusinessOperation();
    Assert.AreEqual("Tons of money",eventFiredExpected);
}

The Implementation

public event Action<string> OperationFinishedNotification; 

public string AwesomeBusinessOperation()
{
    var firstPart = TakesALongTimeToProcess("Tons");
    var secondPart = TakesALongTimeToProcess(firstPart + " of ");
    var result = TakesALongTimeToProcess(secondPart + "money");

    var eventToFire = OperationFinishedNotification;
    if(null!=eventToFire)
    {
        OperationFinishedNotification(result);
    }
    return result;           
} 

private string TakesALongTimeToProcess(string word)
{
    // This operation takes a while
    Thread.Sleep(1000);
    return word;
}

Like before we want to change ‘AwesomeBusinessOperation’ to run asynchronously. Again this is not that hard. Add the async modifier and ‘await’ other asynchronous operations. The rest is the same as before.

public event Action<string> OperationFinishedNotification;

public async Task<string> AwesomeBusinessOperationAsync()
{
    var firstPart =await TakesALongTimeToProcess("Tons");
    var secondPart = await TakesALongTimeToProcess(firstPart + " of ");
    var result = await TakesALongTimeToProcess(secondPart + "money");

    var eventToFire = OperationFinishedNotification;
    if(null!=eventToFire)
    {
        OperationFinishedNotification(result);
    }
    return result;           
}

private Task<string> TakesALongTimeToProcess(string word)
{
    // Remember, this is just a simulation
    // Usually you would use some other async API here
    return TaskEx.Run(() =>
    {
        // This operation takes a while
        Thread.Sleep(1000);
        return word;

    });
}

Since our test is only interested in the event, we don’t need to change the test. How cool is that? However the test fails. Oh oh, bad news =(.

Test fails after making the method asynchronous

Why does our test fail? Well the operation does actually what it’s supposed to do: It runs asynchronously. The code initiates our asynchronous operations and the returns to the caller. In our test it returns to our unit test. This test now checks if the event was called. But the operation which triggers the event is still running and hence the test fails. For our example we can fix this by waiting in the tests. This is possible since our method returns a task anyway. We can use the Wait-method on the task. After this change the test works again.

[Test]
public void ExpectEventToBeFired(){
    var toTest = new MyBusinessLogic();
    var eventFiredExpected = "";
    toTest.OperationFinishedNotification 
        += eventArgument =>
                    {
                        eventFiredExpected = eventArgument;
                    };
    var result = toTest.AwesomeBusinessOperationAsync();
    // just wait for the asynchronous operation
    result.Wait();
    Assert.AreEqual("Tons of money",eventFiredExpected);
}

Conclusion And Follow-Up

Well we’ve seen that some unit tests can easily be adapted for async operations. We’ve also seen that some async operations may break our tests and we need to introduce additional awaits.

As you may noticed this isn’t the full story. We are still using rather naïve examples. What if the async operation returns void and we cannot use the returned task to wait for completion? Does the synchronization really work correctly? There’s more to clearify. I’m going to tackle this issues in follow up post. Stay tuned.

Tagged on: , , ,