Search code examples
c#unit-testingasynchronousasync-awaittypemock-isolator

Is it incorrect to call async methods synchronously in unit tests?


I'm new to the async/await world and I'm trying to figure out the benefits of writing asynchronous unit tests for async methods. That is, must a unit test for an async method call that async method asynchronously? If it calls the async method synchronously using Task.Run(), what is lost? In the latter case, code coverage isn't impacted as far as I can see.

The reason I'm asking this is because our mocking software (we use TypeMock) can't support async/await. (They say there is a legitimate reason for this lack of support, and I don't disagree with them.) By calling the async methods synchronously in the unit tests, we can workaround this issue. However, I'd like to know whether we are cutting any corner by doing this.

For example, let's say I have the following async method:

public async Task<string> GetContentAsync(string source)
{
    string result = "";
    // perform magical async IO bound work here to populate result
    return result;
}

The following is the ideal unit test which doesn't work:

[TestMethod()]
public async Task GetContentAsyncTest()
{
    string expected = "thisworks";
    var worker = new Worker();
    // ...mocking code here that doesn't work!
    string actual = await worker.GetContentAsync();
    Assert.AreEqual(expected, actual);
}

But this works, and it does provide the code coverage we need. Is this OK?

[TestMethod()]
public void GetContentAsyncTest()
{
    string expected = "thisworks";
    var worker = new Worker();
    // mocking code here that works!
    string actual = Task.Run(() => worker.GetContentAsync()).Result;
    Assert.AreEqual(expected, actual);
}

Solution

  • must a unit test for an async method call that async method asynchronously?

    No, but it is most natural to do so.

    If it calls the async method synchronously using Task.Run(), what is lost?

    Nothing really. It's slightly less performant, but to a degree that you will probably never notice.

    You'd probably want to use GetAwaiter().GetResult() instead of Result to avoid AggregateException wrappers in your failure tests. And you can also just call the method directly; no need to wrap it in a Task.Run.

    They say there is a legitimate reason for this lack of support, and I don't disagree with them.

    Oh, I certainly disagree with them. :)

    Does that mean they can't unit test iterator blocks, either? The exact same reasoning would apply...


    The only more serious problem with not supporting async unit tests is if the code under test assumed its context would handle synchronization. This is common, for example, in moderately complex View Models.

    In that case, you'd need to install a context in which to execute the async code (e.g., my AsyncContext type) unless you're using a unit test framework that automatically provides one (as of this writing, only xUnit does AFAIK).