Unit testing asynchronous function
Asked Answered
M

2

21

In the following code sample I have an Async Calculator class. This is injected with an ICalc, which will be a syncronous calculator. I use dependency injecting and mock the ICalc because this resembles my true scenario, though I guess the mocking isn't really of relevance to the question. The AsyncCalc has a function which will call another function asynchronously - taking a callback as parameter. And when the async function call finishes the callback will be triggered with the result.

Now I want to test my asynchronous function - checking that the callback is triggered with the expected parameter. This code seems to work. However, I feel like it might blow up at any time - and my concern is race condition of the callback to finish before the function ends and the test is terminated - as this will be run in a separate thread.

My question now is if I'm on the right track unit testing the async function, or if anyone can help me get on the right track..? What would feel better is if I could ensure that the callback is triggered right away - and preferably on the same thread I guess? Can/Should it be done?

public interface ICalc
{
    int AddNumbers(int a, int b);
}

public class AsyncCalc
{
    private readonly ICalc _calc;
    public delegate void ResultProcessor(int result);
    public delegate int AddNumbersAsyncCaller(int a, int b);

    public AsyncCalc(ICalc calc)
    {
        _calc = calc; 
    }

    public void AddNumbers(int a, int b, ResultProcessor resultProcessor)
    {
        var caller = new AddNumbersAsyncCaller(_calc.AddNumbers);
        caller.BeginInvoke(a, b, new AsyncCallback(AddNumbersCallbackMethod), resultProcessor);
    }

    public void AddNumbersCallbackMethod(IAsyncResult ar)
    {
        var result = (AsyncResult)ar;
        var caller = (AddNumbersAsyncCaller)result.AsyncDelegate;
        var resultFromAdd = caller.EndInvoke(ar);

        var resultProcessor = ar.AsyncState as ResultProcessor;
        if (resultProcessor == null) return;

        resultProcessor(resultFromAdd);
    }             
}

[Test]
public void TestingAsyncCalc()
{
    var mocks = new MockRepository();
    var fakeCalc = mocks.DynamicMock<ICalc>();

    using (mocks.Record())
    {
        fakeCalc.AddNumbers(1, 2);
        LastCall.Return(3);
    }

    var asyncCalc = new AsyncCalc(fakeCalc);
    asyncCalc.AddNumbers(1, 2, TestResultProcessor);
}

public void TestResultProcessor(int result)
{
    Assert.AreEqual(3, result);
}
Mareld answered 21/1, 2010 at 23:8 Comment(1)
I would think your test would simply have to wait in a loop until the callback executes.Eagleeyed
N
23

You could use a ManualResetEvent to synchronize your threads.

In the following example, the test thread will block on the call to completion.WaitOne(). The callback for the async calculation stores the result and then signals the event by calling completion.Set().

[Test]
public void TestingAsyncCalc()
{
    var mocks = new MockRepository();
    var fakeCalc = mocks.DynamicMock<ICalc>();

    using (mocks.Record())
    {
        fakeCalc.AddNumbers(1, 2);
        LastCall.Return(3);
    }

    var asyncCalc = new AsyncCalc(fakeCalc);

    var completion = new ManualResetEvent(false);
    int result = 0;
    asyncCalc.AddNumbers(1, 2, r => { result = r; completion.Set(); });
    completion.WaitOne();

    Assert.AreEqual(3, calcResult);
}

// ** USING AN ANONYMOUS METHOD INSTEAD
// public void TestResultProcessor(int result)
// {
//     Assert.AreEqual(3, result);
// }
Nanettenani answered 21/1, 2010 at 23:26 Comment(1)
I would only add that for certain scenarios (external service) you might want to add a timeout: Assert.IsTrue(completion.WaitOne(TimeSpan.FromSeconds(10)), "Calculation completed");Inclination
S
0

You could also use a "test runner" class to run the asserts in a loop. The loop would run the asserts in a try/catch. The exception handler would simply try to run the asserts again until a timeout has expired. I recently wrote a blog post about this technique. The example is in groovy, but is applicable to any language. Instead of passing a Closure, you would pass a Action in c#.

http://www.greenmoonsoftware.com/2013/08/asynchronous-functional-testing/

Spermatid answered 15/8, 2013 at 2:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.