Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing asynchronous function

Tags:

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);
}
like image 829
stiank81 Avatar asked Jan 21 '10 23:01

stiank81


1 Answers

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);
// }
like image 175
Dave Cluderay Avatar answered Oct 08 '22 06:10

Dave Cluderay