How do you test asynchronous code? Part I

This is the first 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 part II)

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 I

Most developers know that external resources should be accessed asynchronously over the network. Yet some frameworks like Silverlight don’t support synchronous access. This article answers the question of how to test asynchronous methods that use internal or sealed classes from the .NET Framework.

 

The .NET Framework offers asynchronous access for longer running resource accesses. For example, the class WebClient from the System.Net namespace has methods to load data from the web in the background. In principle this is a good thing because you need those asynchronous methods if you don’t want your UI freezing while loading the data. If the download is started synchronous the UI would have to wait for the incoming data. Splitting the download and the UI into two parallel running threads is a must to get a reactive user interface.

Typical code snippets for asynchronous access look like this:

public void LoadArticles() {

  var webClient = new WebClient();

  webClient.DownloadDataCompleted += OnCompleted;

  var url = new Uri("http://example.de/articles");

  webClient.DownloadDataAsync(url);

}

private static void OnCompleted(object sender, DownloadDataCompletedEventArgs e) {

  byte[] articlesXml = e.Result;

  // do something with the XML 

}

It’s necessary to know that the method call to DownloadDataAsync returns right after the call. The download operation is then moved to the background into its own thread. After the download is completed (or an error occurs) the event DownloadDataCompleted will be raised, so you need to bind a method to this event that handles the incoming data.

webClient.DownloadDataCompleted += OnCompleted;

The data that was loaded in the background is handed to this method by the event argument. It contains a field Result that contains the data as a byte array. If an error occurs this is signaled in the field Error. It will then contain an Exception. Check for an error before accessing the data.

It’s always a good idea to encapsulate resource accesses behind a repository. This way you can define an interface for the repository and it’s easy to mock it away in unit tests. But if you implement a repository for the shown LoadArticles method make sure that your repository is asynchronous too. Otherwise you are back in the synchronous world – needing to use the same method pattern plus an event in the repository interface:

public interface IArticleRepository
{

  void LoadArticles();

  event Action<IEnumerable<Article>> OnArticlesLoaded;

}

The pattern shown here for the WebClient class is used in the .NET Framework on many other places. So how should one use this infrastructure so that it can be tested automatically? There are two challenges:

• How to test usages of WebClient without really starting a download from a server

• What to do with the parallel execution of the test and the WebClient call

The answer to the first question is easy: in order to use a dummy resource instead of the real one we need to mock the access to WebClient. The second problem is more difficult because the parallel execution isn’t the root cause of the problem. But let’s first focus on the first problem, the mocking of a WebClient.

A mock for WebClient

In the example code the WebClient instance is created inside the LoadArticles method by using new. This is considered by some as the normal way of instantiating an object. The problem with the example is that the usage of the class WebClient is now hardwired inside of the method. We need to inject an instance to be able to use a mock instead of the real WebClient class. Then we can inject a special testing mock while unit testing our ArticleRepository. We can accomplish this by adding an additional constructor as shown in the following snippet:

public ArticleRepository()
  :this(new WebClient()) {
}

internal ArticleRepository(WebClient webClient) {

  this.webClient = webClient;

}

Only the default constructor is made public. The constructor that is used for testing purposes only is made internal. Clients of the class need not be changed because they still use the parameterless default constructor. In order to use the internal constructor in our unit test project (you separate tests from implementation, don’t you?) we need to add an additional attribute to the implementation project:

[assembly: InternalsVisibleTo("myAssembly.Tests")]

With this attribute in place our testing assembly can use the internals from the implementation assembly. Clients of the implementation assembly only have access to the public interface.

Back to injecting the WebClient: you may have noticed that the semantics of the class were changed slightly by injecting one WebClient instance in the internal constructor. In the former version we instantiated a new WebClient object in each call to the LoadArticles method. To get back to these semantics we can use a little lambda expression:

public ArticleRepository()
  :this(() => new WebClient()) {
}

internal ArticleRepository(Func<WebClient> webClientFactory) {

  this.webClientFactory = webClientFactory;

}

Now we don’t inject a WebClient instance but, instead, a function that yields a WebClient instance. The LoadArticles method now has to use this function instead of new to get a fresh WebClient object:

var webClient = webClientFactory();

This factory method gives us the flexibility we need in unit testing the code.

This is the end of part I. Tune in next week for part II.

Gil Zilberfeld

TOP