Faking a long running asynchronous call

A few days ago I needed to make sure that a specific method would only get called once no matter how many times it’s caller is invoked. The simplified code looked something like this:
public async Task CallLongOperation()
{
    var result = await _client.LongWebCall();

    if (result.Failed)
    {
        // log failure and exit
    }    
}
I needed to make sure that the client was invoked just once no matter how many times CallLongOperation gets called – and to make things interesting the client’s method is asynchronous. Usually when I want to fake a long running call I use WaitHandle but in this case I had something even better…

During previous work I’ve used TaskCompletionSource in order to enable async calls where needed and it seemed only logical that the same method would work for my unit tests:
[TestMethod]
public void CallLongOperation_DuringOperationCallAgain_CalledOnlyOnce()
{
    var fakeClient = A.Fake<IClient>();

    var completionSource = new TaskCompletionSource<Result>();
    A.CallTo(() => fakeClient.LongWebCall()).Returns(completionSource.Task);

    var cut = new ClassUnderTest(fakeClient);
    
    cut.CallLongOperation();
    cut.CallLongOperation();

    completionSource.SetResult(new Result());
    A.CallTo(() => fakeClient.LongWebCall()).MustHaveHappened(Repeated.Exactly.Once);
}
Using the TaskCompletionSource (line 6) help me simulate an async call that won’t return until I call SetResult on it (line 14) and since I do not muse await on the actual call I write the whole test without creating any additional threads – creating a simple and deterministic unit test.
It amazed me on how simple it become testing methods that uses async/await - just because the code uses asynchronous calls does not mean that its unit tests need to be complicated or unpredictable.

Happy coding...

Labels: , , , , ,