TaskCompletionSource in real life (part 1 of 2)

This is the first of two parts. The second one is here: TaskCompletionSource in real life (part 2 of 2)

The first time I came in contact with the TaskCompletionSource, it took me a little while to realize its use. I see the same thing happening when I happen to introduce it to someone else as well. It's a bit like when your younger kids come home from nursery school, proudly showing to a drawing they made: "Wow! Pretty! ... Um, what is it?"

So I thought I'd share a couple of real-world examples of when I find the TaskCompletionSource to be very useful.

Faking tasks for testing purposes

Well-designed unit tests communicate a lot of information. If they pass, they inform you that a certain behavior works as expected in your software. If it fails, a well-designed test will give you a rather detailed idea about what it is that is not working. For this reason it's important to be quite aware of what you are actually testing.

Let's say that we have some piece of code that needs to download some document from the internet, and do something with that information. Now we want to write some tests for that functionality. What we want to test is:

  1. What happens when the document is successfully downloaded?
  2. What happens if the download fails?

This is what we want to test. What we do not want to test is the download mechanism itself. We don't want our test to fail because the build server doesn't have an Internet connection, or because the URL that you chose for testing happens to be unreachable. Obviously, we want to mock that part of the code. In this example, the object under test will require the calling code to inject an object that is used for downloading the content. For that purpose I made the following interface:

public interface IAsyncWebClient
{
    Task GetStringAsync(Uri uri);
}

As you can see, it represents functionality that can start an asynchronous download, returning a Task for handling the result.

Here is a simple example of that (using Moq). The Worker class has a method called ProcessUri, that will return true or false indicating whether it was successful or not:

[Test]
public void ProcessUri_DownloadSucceeds_True()
{
    // Create a TaskCompletionSource for the test
    var tcs = new TaskCompletionSource();
    // Create a mock for the IAsyncWebClient interface
    var asyncWebClientMock = new Mock();
    var uri = new Uri("file://fake");

    // Configure the web client mock for calls to the GetStringAsync method
    // with the given uri. When invoked, it will assign the string "content"
    // as the result of the task from the TaskCompletionSource. We also configure
    // the mock to return the Task object from the TaskCompletionSource.
    asyncWebClientMock
        .Setup(mock => mock.GetStringAsync(uri))
        .Callback(() => tcs.TrySetResult("content"))
        .Returns(tcs.Task);

    // Create the Worker to test, passing in the mocked web client
    var worker = new Worker(asyncWebClientMock.Object);
    // Call the ProcessUri method and wait for the result.
    var result = worker.ProcessUri(uri).Result;

    // Verify that the result is true
    Assert.IsTrue(result);
}

That is a simple test for a happy-flow scenario. And here is a very simple (and naïve) implementation that makes the test pass:

public class Worker
{
    private readonly IAsyncWebClient asyncWebClient;
    public Worker(IAsyncWebClient asyncWebClient)
    {
        this.asyncWebClient = asyncWebClient;
    }

    public Task ProcessUri(Uri uri)
    {
        return asyncWebClient
            .GetStringAsync(uri)
            .ContinueWith(task => task.Result.Length > 0);
    }
}

As you can see, ProcessUri will blindly assume that the GetStringAsync task will succeed. Let’s add another test for the fail scenario; if the download throws a WebException:

[Test]
public void ProcessUri_DownloadFails_False()
{
    // Arrange
    var tcs = new TaskCompletionSource();
    var asyncWebClientMock = new Mock();
    var uri = new Uri("file://fake");

    asyncWebClientMock
        .Setup(mock => mock.GetStringAsync(uri))
        .Callback(() => tcs.TrySetException(new WebException()))
        .Returns(tcs.Task);

    // Act
    var worker = new Worker(asyncWebClientMock.Object);
    var result = worker.ProcessUri(uri).Result;

    // Assert
    Assert.IsFalse(result);
}

As you can see, this time we use the Callback method of the mock to assign a WebException as the result of the task from the TaskCompletionSource. The ProcessUri method is updated to make that (and the previous) test pass:

public Task ProcessUri(Uri uri)
{
    return asyncWebClient
        .GetStringAsync(uri)
        .ContinueWith(task =>
        {
            if (task.IsFaulted)
            {
                return false;
            }
            return task.Result.Length > 0;
        });
}

Now you have seen an example of how to use TaskCompletionSource to simulate asynchronous tasks for testing purposes, giving you the ability to mock Task objects and their result. This is one of the two main cases where I use it in my daily work. Tomorrow I will present the other case. Oh the suspense.