I want to unit test a method that I have that performs and async operation:
Task.Factory.StartNew(() =>
{
// method to test and return value
var result = LongRunningOperation();
});
I stub the necessary methods etc in my unit test (written in c#) but the problem is that the async operation is not finished before I assert the test.
How can I get around this? Should I create a mock of the TaskFactory or any other tips to unit testing an async operation?
Unlike async void unit tests that are quite complicated, you can have async Task unit tests, i.e., unit tests that return a Task instance. Almost all the unit test frameworks (MSTest, NUnit, etc.) provide support for such unit tests.
This illustrates the first lesson from the async/await conceptual model: To test an asynchronous method's behavior, you must observe the task it returns. The best way to do this is to await the task returned from the method under test.
To test asynchronous code, we use the XCTestExpectation class and wait for the expected outcome. The workflow is to create an expectation, and then when the asynchronous task completes successfully, we fulfil that expectation. We will wait for a specific amount of time for the expectation to be fulfilled.
Asynchronous is an online environment where interaction does not take place at the same time, while a synchronous environment provides real time learning. While both have advantages and disadvantages, asynchronous is more widely used in administering assessments.
You'd have to have some way of faking out the task creation.
If you moved the Task.Factory.StartNew
call to some dependency (ILongRunningOperationStarter
) then you could create an alternative implementation which used TaskCompletionSource
to create tasks which complete exactly where you want them to.
It can get a bit hairy, but it can be done. I blogged about this a while ago - unit testing a method which received tasks to start with, which of course made things easier. It's in the context of async/await in C# 5, but the same principles apply.
If you don't want to fake out the whole of the task creation, you could replace the task factory, and control the timing that way - but I suspect that would be even hairier, to be honest.
I would propose to stub a TaskScheduler in your method with a special implementation for unit tests. You need to prepare your code to use an injected TaskScheduler:
private TaskScheduler taskScheduler;
public void OperationAsync()
{
Task.Factory.StartNew(
LongRunningOperation,
new CancellationToken(),
TaskCreationOptions.None,
taskScheduler);
}
In your unit test you can use the DeterministicTaskScheduler described in this blog post to run the new task on the current thread. Your 'async' operation will be finished before you hit your first assert statement:
[Test]
public void ShouldExecuteLongRunningOperation()
{
// Arrange: Inject task scheduler into class under test.
DeterministicTaskScheduler taskScheduler = new DeterministicTaskScheduler();
MyClass mc = new MyClass(taskScheduler);
// Act: Let async operation create new task
mc.OperationAsync();
// Act: Execute task on the current thread.
taskScheduler.RunTasksUntilIdle();
// Assert
...
}
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