How do you test asynchronous code? Part II

This is the second part of Stefan Lieser’s article appearing in DotNetPro. Stefan was generdot_Logo_SchwarzRot_170_41ous to translate the article and post it here. (Read the first part)

Stefan Lieser works as a consultant, trainer and author. He is interested in agile methods and looking for improvement every day. Together with Ralf Westphal he started the Clean Code Developer initiative (see http://clean-code-developer.de) which gains lots of interest in both the .NET Stefan-Lieserand Java software developer community. Furthermore he was one of the organizers of the .NET Open Space in Leipzig, Germany.

How do you test asynchronous code? Part II

It‘s all faked

We can now use the additional constructor to inject a mocked WebClient instance so we don’t start a real download in the automated tests. To be able to inject a mock in tests and “the real thing” in production we need a common interface for both. Unfortunately, Microsoft decided that WebClient would not implement any interface. It only inherits from Component which is of no use in this context. I see two ways out of this dilemma: the first is to define our own interface. That means implementing a wrapper class around WebClient that implements our interface and uses WebClient internally to do its job. Writing such wrappers is not only dull but also more difficult: class DownloadDataCompletedEventArgs has only internal constructors so we can’t use it in our wrapper. Going the wrapper route means not only wrapping WebClient but implementing our own EventArgs class. What those wrappers may look like is shown by the following code snippets:

public interface IWebClient
{
    event EventHandler<DownloadDataCompletedEventArgsWrapper> DownloadDataCompleted;

    void DownloadDataAsync(Uri address);
}

public class WebClientWrapper : IWebClient
{
    private readonly WebClient webClient = new WebClient();

    public WebClientWrapper() {
        webClient.DownloadDataCompleted += ProcessDownloadDataCompleted;
    }

    private void ProcessDownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e) {
        DownloadDataCompleted(sender, new DownloadDataCompletedEventArgsWrapper(e));
    }

    public event EventHandler<DownloadDataCompletedEventArgsWrapper> DownloadDataCompleted = delegate { };

    public void DownloadDataAsync(Uri address) {
        webClient.DownloadDataAsync(address);
    }
}

The real WebClient class is used inside the wrapper to do the work. The methods from the wrapper refer to the WebClient instance to do their work. If you don’t want to write those wrappers you will need a powerful testing tool.

Mocking with the Profiler API

Instead of writing wrappers for infrastructure classes from the .NET Framework we can use powerful technologies like the Profiler API to be able to mock the objects. By using the Profiler API one can intercept nearly every call. That’s what the popular Typemock Isolator uses internally.

I’m still a fan of injecting dependencies as mentioned above with an internal constructor to make the tests more expressive. Typemock Isolator, however, can intercept even the call to new inside of the method. Using Typemock Isolator the automated test looks like this:

[TestFixture]
public class ArticleRepositoryTests
{
    private ArticleRepository sut;
    private WebClient webClient;
    private IArticleReader articleReader;

    [SetUp]
    public void Setup() {
        webClient = Isolate.Fake.Instance<WebClient>;
        Isolate.WhenCalled(() => webClient.DownloadDataCompleted += null).CallOriginal();

        articleReader = Isolate.Fake.Instance<IArticleReader>();

        sut = new ArticleRepository(() => webClient, articleReader);
    }

    [Test, Isolated]
    public void WebClient_Data_is_handled_by_Reader() {
        var e = Isolate.Fake.Instance<DownloadDataCompletedEventArgs>();

        var count = 0;
        sut.OnArticleLoaded += delegate { count++; };

        sut.LoadArticles();
        Isolate.Invoke.Event(() => webClient.DownloadDataCompleted += null, null, e);

        Assert.That(count, Is.EqualTo(1));
    }
}

In the example we created a WebClient instance using the following snippet:

webClient = Isolate.Fake.Instance<WebClient>;

Because we used Isolator to instantiate the object we were able to modify its behavior in the test. Isolator intercepted the call to DownloadDataAsync. The download didn’t really start and we isolated our repository from the real resource.

In order to give the repository some test data we had to invoke the DownloadDataCompleted event. And doing this meant that we needed an instance of the event argument. As mentioned above this had an internal constructor but with Isolator to the rescue we solved this with the following snippet:

var e = Isolate.Fake.Instance<DownloadDataCompletedEventArgs>();

After creating the event argument we told Isolator to raise the event:

Isolate.Invoke.Event(() => webClient.DownloadDataCompleted += null, null, e);

In the end this test shows that the data that is transmitted by the WebClient to our repository is received by the object that handles the data.

What about asynchronicity?

But now back to the problem of testing the asynchronous call. As shown in the code example the ArticleRepository uses WebClient to download the XML data and creates article objects from that data. The repository itself should not map from XML to article objects. If it did, the tests would be much more difficult because the data would have to go through the WebClient mock.

In order to separate the mapping concern we could inject another object into the repository. The responsibility of the repository would then be to download the data by using the WebClient and hand it over to a mapper to create article objects from it. So let’s create an interface for that:

public interface IArticleReader
{
    IEnumerable<Article> Read(byte[] articleXml);
}

The IArticleReader maps from a byte-array containing the XML to an enumerable of articles. That’s pretty easy to test.

The only thing we have to test for the repository is if it hands the downloaded data to the IArticleReader object:

[Test, Isolated]
public void WebClient_Data_is_handed_to_article_reader() {
    var e = Isolate.Fake.Instance<DownloadDataCompletedEventArgs>();
    var downloadedData = new byte[0];
    Isolate.WhenCalled(() => e.Result).WillReturn(downloadedData);

    sut.LoadArticles();
    Isolate.Invoke.Event(() => webClient.DownloadDataCompleted += null, null, e);

    Isolate.Verify.WasCalledWithExactArguments(() => articleReader.Read(downloadedData));
}

Where have the async problems gone? They have disappeared because we separated the concerns of the repository. We now only need an integration test that verifies we can really download the data from a real server and deliver the correct article objects. And of course that integration test needs to handle the asynchronous method call

The problem with the integration test is that after calling LoadArticles the method immediately returns, forcing us to wait for the result. But how long should we wait, ten seconds? That may be enough on a developer machine but not on the build server. Waiting even longer means slowing down the tests so using Thread.Sleep is not a good idea.

I recommend using an EventWaitHandle which makes it possible to wait for the event or a timeout. The test looks like this:

[Test]
public void Articles_get_loaded() {
    var waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
    var count = 0;
    sut.OnArticleLoaded += delegate(IEnumerable<Article> articles) {
        count++;
        Assert.That(articles, Is.Not.Null);
        waitHandle.Set();
    };

    sut.LoadArticles();
    var wasSignalled = waitHandle.WaitOne(5000);

    Assert.That(wasSignalled, Is.True, "A timeout occurred");
    Assert.That(count, Is.EqualTo(1));
}

Conclusion

Testing is easy if the basic principles for inner software quality are followed. In this example the Separation of Concerns leads to code that is easy to test. Of course, principles need to be followed in the context of a value system. If the only value were correctness than writing the wrapper classes would not be a problem. There is, however, the value of production efficiency. It’s more efficient to use tools like Typemock Isolator to overcome technical limitations. You may decide not to inject every dependency and that’s fine, as long as it’s good for efficiency and does no harm to correctness and evolvability.

Gil Zilberfeld

TOP