Unit-testing Asynchronous Events

Once in the realm of asynchronous calls, we have no guarantee of which thread or precisely when an event handler might be called, which makes testing asynchronous events that much more difficult. As a result it makes the test shown below (taken from the previous post unit-testing synchronous events) completely redundant because we rely on the assertion being called after (and only after) the event has fired, which this does not do.

In a synchronous event situation, this test works because the thread which calls basket.Add is the same thread which executes the lambda expression, therefore it doesn’t return from the Add method until after local variable eventFired is set to true. However there are no such guarantees with an asynchronous event.

Because an asynchronous event handler will execute on a different thread to the one which initiated the call (that caused the event to fire) we need some mechanism to stop the original thread from performing any assertions until after the event has fired. There is more than one way to achieve this, but for this example the mechanism comes in the form of an EventWaitHandle which allows one thread to ‘signal’ another thread that something has happened. In this particular case I’m using the EventWaitHandle sub-class ManualResetEvent, which as its name suggests remains signalled until it is manually set back to non-signalled.

The example below tests a class called BackgroundTask (not shown) which performs an operation asynchronously on a separate worker thread and then fires the TaskComplete event once the task has finished. Because it’s asynchronous, the call to task.DoCalculationAsync returns almost immediately which is the issue we need to solve.

Rather than the lambda expression setting a boolean value, this time it sets the ManualResetEvent to signalled via a call to its Set method. Meanwhile the original thread which called task.DoCalculationAsync will block on the call to WaitOne until (a) the wait handle becomes signalled, or (b) the 30 second timeout expires. If the wait handle is signalled within the 30 second time limit, then eventFired will be set to true but if the timeout expires, it will be set to false and the test will fail.

Timeouts and continuous integration
The previous section raises the obvious issue of what is an acceptable timeout limit in your tests. I included an arbitrary timeout value in the above example just to show the possible options, but if you are testing a background task which might vary in how long it takes to complete, then use a larger value (e.g. 5 minutes) or don’t specify one at all which means it will never timeout. However just bear in mind that this might cause big delays or a complete stand-still on your CI server, so having a ‘fail-safe’ timeout is probably a good idea. The time it takes for tests to complete on your CI environment will vary due to the workload on the server and this should be taken into consideration. A test on your development machine might take twice as long to run on the CI because there might be several build agents running on the same box / virtual.

Advertisements

About Phil Munro

I have been developing commercial desktop and distributed web applications with Microsoft technologies since 1997.
This entry was posted in C#, Multi-threading, NUnit, TDD. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s