Last time we explored some implications of the C# asynchronous operations for unit tests. We noticed that we need to orchestrate tests to honor the asynchronous nature of our code. This time we take a look at another pit fall. Again this example assumes that we’re developing a small desktop application.
Let’s start: This time our business operation sums up a value, let’s say it sums up money. Since it’s hard to earn a Dollar/Euro/Swiss Franc it takes a while. And to earn more we do this money earning process twice. Our synchronous code works just fine.
As said the method ‘EarnOneDollar’ takes a while. To improve the reactiveness of our desktop application we make this process asynchronous. First we make the ‘EarnOneDollar’-method asynchronous. In the ‘EarnMoney-‘method we start the two sub task asynchronously and then wait until everything is done. In the test we wait for the result and then check if the property has the right value.
A Race Condition, Oh noes!
Obviously we’ve introduced a race condition. However when that code runs in the regular desktop application it works fine. So why does it fail in the test setup? Remember my post about the synchronization context? This bites us here: Since there’s no synchronization context in the NUnit test-runner the continuations are executed on the thread-pool. This also means that the synchronization of our money counter variable is wrong. How do we fix that? Well we could synchronize the variable, but that’s quite ugly and bloats the code. I suggest to setup the environment so that it mimics a desktop application. This means that there’s a message pump and a synchronization context.
Let’s introduce a special ‘test’-message pump. It is based on the example implementation from my previous post. This message pump processes messages until some special condition is matched. We create a static utility method which sets up the message pump and sets it as the synchronization context. This method expects a closure which contains the test to run. Additionally it passes an ‘awaiter’ to that test-closure. The ‘awaiter’ allows us to wait on a task and process messages meanwhile.
The Test Utility
Now we’re ready to update the test. We use this nice utility to provide a synchronization context for our test. We wrap our test in a closure and use the awaiter to wait for the process to finish
Conclusion And Follow-Up
This time we’ve looked at an example where the unit test fails due to invalid synchronization. It’s really important to understand the concept of the synchronization context also in unit tests. To ensure correct synchronization we’ve also introduced a utility class which sets up a special synchronization context for a test.
There’s more to come about async operations and unit testing. Meanwhile: Have a fun time playing with the Async CTP yourself.
- C# 5.0 Async-Feature: Unit Testing, Part I
- The Wire Season 4-5