C# 5.0 Async-Feature: Unit Testing Part III, Avoid Void.

C# Learns From F#

C# Learns From F#

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

public event Action GoodsArrived;

public void SendGoodsToCostumers(string theGoods)
{
    var package = PreparePackage(theGoods);
    SendPackage(package);
    GoodsArrived();
}

private string SendPackage(string package)
{
    // This operation takes a while
    Thread.Sleep(50);
    return "Send: " + package;
            
}

private string PreparePackage(string theGoods)
{
    // This operation takes a while
    Thread.Sleep(50);
    return "Wrapped: " + theGoods;
}

The Test

[Test]
public void SendGoodsToCostumers()
{
    var toTest = new ComplexBusinessOperations();

    var wasSent = false;
    toTest.GoodsArrived += () => wasSent = true;
    toTest.SendGoodsToCostumers("A Nice Toy");
    Assert.IsTrue(wasSent);
}

As expected everything runs smooth. Now we’re turning the method into an asynchronous implementation

public event Action GoodsArrived;

public async void SendGoodsToCostumersAsync(string theGoods)
{
    var package = await PreparePackageAsync(theGoods);
    var sentPackage = await SendPackageAsync(package);
    GoodsArrived();
}

private Task<string> SendPackageAsync(string package)
{
    // Remember, this is just a simulation
    // Usually you would use some other async API here
    return TaskEx.Run(() =>
                            {
                                // This operation takes a while
                                Thread.Sleep(50);
                                return "Send: " + package;
                            });
}

private Task<string> PreparePackageAsync(string theGoods)
{
    // Remember, this is just a simulation
    // Usually you would use some other async API here
    return TaskEx.Run(() =>
                            {
                                // This operation takes a while
                                Thread.Sleep(50);
                                return "Wrapped:: " + theGoods;
                            });
}

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

// Return a Taks instead of void to make testing easier
public async Task SendGoodsToCostumersAsync(string theGoods)
{
    var package = await PreparePackageAsync(theGoods);
    var sentPackage = await SendPackageAsync(package);
    GoodsArrived();
}

The Test

[Test]
public void SendGoodsToCostumers()
{
    var toTest = new ComplexBusinessOperations();

    var wasSent = false;
    toTest.GoodsArrived += () => wasSent = true;
    toTest.SendGoodsToCostumersAsync("A Nice Toy").Wait();
    Assert.IsTrue(wasSent);
}

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

public class Awaiter
{
    private readonly MyPrimitiveSynchronisationContext syncContext;

    internal Awaiter(MyPrimitiveSynchronisationContext syncContext)
    {
        this.syncContext = syncContext;
    }

    public void WaitFor(IAsyncResult toWaitOn)
    {
        WaitFor(() => toWaitOn.IsCompleted);      
    }
    // Our new wait method which can wait for anything
    // Continues when the condition is true
    public void WaitFor(Func<bool> toWaitOn)
    {
        while (!toWaitOn())
        {
            syncContext.RunOneRound();
        }
    }
}

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

[Test]
public void SendGoodsToCostumers()
{
    TestSyncContext.Run((awaiter)=>
    {
        var toTest = new ComplexBusinessOperations();

        var wasSent = false;
        toTest.GoodsArrived += () => wasSent = true;
        toTest.SendGoodsToCostumersAsync("A Nice Toy");
        awaiter.WaitFor(()=>wasSent);
        Assert.IsTrue(wasSent);
                                
    });
}

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.

Tagged on: , , ,

6 thoughts on “C# 5.0 Async-Feature: Unit Testing Part III, Avoid Void.

  1. Mark Bestland

    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 😉

  2. Ben

    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 🙂

  3. gamlerhart Post author

    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.

  4. Jimmy Strube

    I like this approach very much

    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 .

    … but I do need help.
    It is desirable to have these tests fail or throw an exception on a timeout rather than run forever. Especially when a build server is running the test suite on checkin. Without an exception on timeout, the build hangs. Have you written or have you seen a fix to that would throw an exception on a timeout?

  5. gamlerhart Post author

    I did not. I think the newer realease of NUnit, MSTest etc have some built in support. I would first have to check out what they provide and then move from there. I’m currently busy on the JVM / Scala platform and rarely touch .NET

Leave a Reply

Your email address will not be published. Required fields are marked *