Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why would one need "async" support for MS Unit Test if Task.Result can be used?

I'm a bit puzzled here...I have a test method which does a call to an async method and I changed the signature to async Task. Works.

[TestMethod]
public async Task TestIt()
{
  bool result = await service.SomethingAsync();
  Assert(result);
}

Now I read all over the web that support for async is required in unit tests and is now also in NUnit. But why is that so important? I can write the test like this, using the Result property which will wait for Task completion:

[TestMethod]
public void TestIt()
{
  bool result = service.SomethingAsync().Result;
  Assert(result);
}
like image 259
Krumelur Avatar asked Dec 12 '13 08:12

Krumelur


People also ask

Why is async needed?

Note: The purpose of async / await is to simplify the syntax necessary to consume promise-based APIs. The behavior of async / await is similar to combining generators and promises. Async functions always return a promise.

Should unit tests be async?

Moreover, support for async void unit tests varies across frameworks, and even framework versions. For these reasons, it's best to avoid async void unit tests.

What does an async task do?

An asynchronous task is defined by a computation that runs on a background thread and whose result is published on the UI thread. An asynchronous task is defined by 3 generic types, called Params , Progress and Result , and 4 steps, called onPreExecute , doInBackground , onProgressUpdate and onPostExecute .

Should I use async or not?

Asynchronous loops are necessary when there is a large number of iterations involved or when the operations within the loop are complex. But for simple tasks like iterating through a small array, there is no reason to overcomplicate things by using a complex recursive function.


1 Answers

Result has an unfortunate side effect of wrapping all exceptions in AggregateException. This makes testing the error path much more painful.

But even if you decide you could live with that, you still have the problem of calling multiple async methods within a single test; i.e., if your test setup also requires async work. To do it in a blocking way, you'd have to either refactor your test method into a separate async method, or wrap it in an async delegate and either execute it directly or toss it into Task.Run. Not impossible, but not convenient either.

Finally, there's the problem of async components that assume a one-thread-at-a-time context. Examples include ViewModels and WebAPI/MVC controllers. At that level, those components often assume that they don't need to synchronize access to asynchronously shared data because they're never executed in a free-threaded context. Until you unit test, that is. The common approach is to give those unit tests a single-threaded context, e.g., installing a Dispatcher on the unit test thread. In that case, a Result will deadlock. There are ways around this but, again, it's not convenient to write that code.

The bottom line is that async unit tests don't make it possible; they make it convenient. And anything that encourages unit testing (especially for trickier things like async) is a good idea. :)

like image 89
Stephen Cleary Avatar answered Sep 28 '22 16:09

Stephen Cleary