I have an ASP.NET Web API application, with an ApiController that features asynchronous methods, returning Task<>
objects and marked with the async
keyword.
public class MyApiController : ApiController
{
public async Task<MyData> GetDataById(string id)
{
...
}
}
How can I write NUnit tests for the ApiController's asynchronous methods? If I need to use another testing framework I'm open for that too. I'm fairly new to .NET unit testing in general, so I'm interested in learning best practices.
It seems to me there is no support built into NUnit 2.6 for testing async methods returning Tasks. The best option I can see right now is to use Visual Studio's own UnitTesting framework or xUnit.net as both support asynchronous tests.
With the Visual Studio UnitTesting framework I can write asynchronous tests like this:
It really depends on what they're doing. Usually they'll be depending on something else which provides a
Task<T>
or something similar. In that case, you may be able to provide fake dependencies which allow you to control all of this in a fine-grained way. I've got a prototype "time machine" which allows you to preprogram tasks to complete at particular artificial times, then move time forward and perform assertions as you go. There's a blog post about it which you may find useful. As I say, it's only a prototype and it's not appropriate for all scenarios - but it may suit you.Stephen Cleary also has a couple of blog posts around unit testing (1, 2), taking a slightly different approach, along with a NuGet package you may find useful.
The basic approach is the same as normal though: give your method different inputs (and dependency outputs) and test the results. It's definitely trickier achieving that with asynchrony, but it's doable.
Bear in mind that the NUnit 2.6 series, like those before it, is built to target the .NET 2.0 framework. So, NUnit can only do the same things you could code yourself, in an assembly targeting .NET 2.0.
Specifically, you can't mark your test method as async and expect NUnit to do anything special with it.
You can, however,
Neither of the above will give you asynchronous test execution, if that's what you are hoping for. No other tests will execute while waiting for the asynchronous operation to complete.
Another option is to use NUnitLite. NUnitLite 0.8 supports the [Asynchronous] attribute which will allow other tests to continue while your asynchronous test completes. The advantage of the attribute is that it allows asynchronous tests to work in .NET 2.0 through 4.0
We don't currently have a .NET 4.5 build of NUnitLite but it will be added soon and we are working on a change that will make use of [Asynchronous] optional in that environment. For now, you can easily download and recompile the source code for .NET 4.5.
For the future, look to NUnit 3.0 to support async methods fully along with general parallel execution of tests on multiple threads or in multiple processes.
As of today (7/2/2014) async testing is supported by:
In the first two frameworks, the test method must have this signature:
NUnit, apart from that signature, supports this one:
Of course, inside any of these test methods you can use
await
to call and wait on asynchronous methods.If you're using a testing framework that doesn't support async test methods, then, the only way to do it, is to call the async method and wait until it finishes running using any of the usual ways:
await
, reading theResult
property of theTask<T>
returned by anasync
method, using any of the usual wait methods ofTask
and so on. After the awaiting, you can do all the asserts as usual. For example, using MSTest:I'm in the process of converting some of my methods to
async
. Getting this to work with NUnit has been quite straightforward.The test methods can not be asynchronous. But we still have access to the full functionality of the Task Parallel Library, we just can't use the
await
keyword directly in the test method.In my example, I had a method:
And it was tested in nUnit like so:
Now that I have altered the method
SendUpdateRequestToPlayer
to be an asynchronousI simply had to modify my tests to
Wait
for the task to complete: