I have the following code which i'd like to test:
private Task _keepAliveTask; // get's assigned by object initializer
public async Task EndSession()
{
_cancellationTokenSource.Cancel(); // cancels the _keepAliveTask
await _logOutCommand.LogOutIfPossible();
await _keepAliveTask;
}
It is important that the EndSession
Task only ends once the `_keepAliveTask' ended. However, I'm struggling to find a way to test it reliably.
Question: How do i unit test the EndSession
method and verify that the Task
returned by EndSession
awaits the _keepAliveTask
.
For demonstration purposes, the unit test could look like that:
public async Task EndSession_MustWaitForKeepAliveTaskToEnd()
{
var keepAliveTask = new Mock<Task>();
// for simplicity sake i slightly differ from the other examples
// by passing the task as method parameter
await EndSession(keepAliveTask);
keepAliveTask.VerifyAwaited(); // this is what i want to achieve
}
Further criterias: - reliable test (always passes when implementation is correct, always fails when implementation is wrong) - cannot take longer than a few milliseconds (it's a unit test, after all).
I have already taken several alternatives into considerations which i'm documenting below:
async
methodIf there wouldn't be the call to _logOutCommand.LogOutIfPossible() it would be quite simple: i'd just remove the async
and return _keepAliveTask
instead of await
ing it:
public Task EndSession()
{
_cancellationTokenSource.Cancel();
return _keepAliveTask;
}
The unit test would look (simplified):
public void EndSession_MustWaitForKeepAliveTaskToEnd()
{
var keepAliveTask = new Mock<Task>();
// for simplicity sake i slightly differ from the other examples
// by passing the task as method parameter
Task returnedTask = EndSession(keepAliveTask);
returnedTask.Should().be(keepAliveTask);
}
However, there's two arguments against this:
Task.WhenAll
further down)EndSession
. Still will have to test it there.Of course, I could do something similar:
public Task EndSession()
{
_cancellationTokenSource.Cancel(); // cancels the _keepAliveTask
_logOutCommand.LogOutIfPossible().Wait();
return _keepAliveTask;
}
But that is a no-go (sync over async). Plus it still has the problems of the previous approach.
async
method using Task.WhenAll(...)
Is a (valid) performance improvement but introduces more complexity: - difficult to get right without hiding a second exception (when both fail) - allows parallel execution
Since performance isn't key here i'd like to avoid the extra complexity. Also, previously mentioned issue that it just moves the (verification) problem to the caller of the EndSession
method applies here, too.
Now of course instead of "unit" testing method calls etc. I could always observe effects. Which is: As long as _keepAliveTask
hasn't ended the EndSession
Task
mustn't end either. But since I can't wait indefinite one has to settle for a timeout. The tests should be fast so a timeout like 5 seconds is a no go. So what I've done is:
[Test]
public void EndSession_MustWaitForKeepAliveTaskToEnd()
{
var keepAlive = new TaskCompletionSource<bool>();
_cancelableLoopingTaskFactory
.Setup(x => x.Start(It.IsAny<ICancelableLoopStep>(), It.IsAny<CancellationToken>()))
.Returns(keepAlive.Task);
_testee.StartSendingKeepAlive();
_testee.EndSession()
.Wait(TimeSpan.FromMilliseconds(20))
.Should().BeFalse();
}
But I really really dislike this approach:
If all you want is to verify that EndSession
is awaiting _keepAliveTask
(and you really have full control over _keepAliveTask
) then you can create your own awaitable type instead of Task
the signals when it's awaited and check that:
public class MyAwaitable
{
public bool IsAwaited;
public MyAwaiter GetAwaiter()
{
return new MyAwaiter(this);
}
}
public class MyAwaiter
{
private readonly MyAwaitable _awaitable;
public MyAwaiter(MyAwaitable awaitable)
{
_awaitable = awaitable;
}
public bool IsCompleted
{
get { return false; }
}
public void GetResult() {}
public void OnCompleted(Action continuation)
{
_awaitable.IsAwaited = true;
}
}
Since all you need to await
something is that has a GetAwaiter
method that returns something with IsCompleted
, OnCompleted
and GetResult
you can use the dummy awaitable to make sure _keepAliveTask
is being awaited:
_keepAliveTask = new MyAwaitable();
EndSession();
_keepAliveTask.IsAwaited.Should().BeTrue();
If you use some mocking framework you can instead make Task
's GetAwaiter
return our MyAwaiter
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With