C# 5.0 Async-Feature: Unit Testing Part III, Avoid Void.
Disclaimer: This post is based on the C# 5.0 CTP. Everything described here is subject to future changes.
Last time I demonstrated that the synchronization context is also relevant for unit testing and that you might need to setup one in your tests. This time we’re going to explore what we can do when a asynchronous method returns void instead of a task.
Once again we write some code for our imaginary desktop application. This time we test a method which sends some stuff to costumers. When the package is sent an event is raised. First the synchronous version. It’s straight forward, we package the goods and then send it. When we’re done we fire the event.
The Implementation
The Test
As expected everything runs smooth. Now we’re turning the method into an asynchronous implementation
As soon as we try to update the test-method we’re facing a problem. We cannot wait for a void-result. This means there’s no way to wait for the completion of the task. Well the easiest thing to do is to change the result to return a task. This allows us to wait in the test.
Returning a Task
The Test
In The Case Of Void
Now we’ve seen that when we return a task instead of void we can make our testing-life much easier. But what if that’s not possible? For example because we don’t want to break the API. Or if it’s a library call which we cannot change. Then this gets a little more tricky. When the method returns void we cannot wait for the task to complete. A ugly work around would be to just use Thread.Sleep for a certain time an hope that everything completes meanwhile. However this is a brittle method and prolongs our test unnecessary. What can we do instead? Remember the previous post where we used a special synchronization-context which allowed us to wait on a task? Well does this really have to be a task? I mean we can wait for other things, right?
So the basic idea is to extend our test synchronization context to provide a special wait-method. And while we’re waiting we process other messages. This is actually only a small extension to our existing code
Now we can use this in our test. We run our test-code in the test synchronization context. Then we wait until the condition is fulfilled and finally continue
You’ve probably noticed that this test will run forever instead of failing. Unfortunately we cannot fix that. The only thing we can do is to implement some kind of timeout and maybe use a reasonable default. But you can implement that without my help ;).
Conclusion
In this post I demonstrated that you should always return a task in an asynchronous API instead of void. This allows us to easily test the code and makes it more flexible. In the cases where we have to deal with asynchronous code which doesn’t return a task we have to get creative with special wait conditions.
Now I’ve finished with my little tour through Unit testing with the new asynchronous features. This post series is by no means a complete guide. It’s here to get an impression where the difficulties lie. I think when the async feature ships some knowledge and patterns have already emerged. And maybe we all should take a looks at F#. Because F# has had asynchronous workflows for quite a while. And don’t forget do download and experiment with the Async CTP yourself.
- The Wire Season 4-5
- Magic Cast Method in Java
WasSended ? Did you really use the word Sended ? Maybe I readed it wrongly, or didn’t lookeded at it rightly, or something like that-ish. Dang I’m confused 😉
Oh, my mistake. Should be ‘sent’ of course. Will fix it.
I’ve been using something similar to your Awaiter for a while to help with async testing:
public static void SleepTil(Func func, int maxTime)
{
var endTime = DateTime.Now.AddMilliseconds(maxTime);
while (!func() && DateTime.Now < endTime)
Thread.Sleep(500);
}
Of course the downside is that when your tests fail, you can end up waiting a long time 🙂
Yep, that’s why I usually try to avoid any kind of sleeps etc in my tests. Of course sometime it’s get’s to complex so that a sleeping is a better solution in order to keep the test readable.