1

This (n)unit test fails:

[Test]
public void FooTest()
{
    var foo = new Foo();

    Assert.That(() => foo.DoSomething(), Throws.InstanceOf<Exception>());
}

This is the class under test:

public class Foo : IFoo
{
    public async void DoSomething()
    {
        await DoSomethingAsync();
    }

    private async Task DoSomethingAsync()
    {
        await Task.Run(() =>
    {
            Thread.Sleep(500);
            throw new Exception("exception");
        });
    }
}

If I change DoSomething this:

public void DoSomething()
{
    DoSomethingAsync().Wait();
}

Then the test will pass. Is there any way I can avoid having to do that?

Note: I cannot change the signature of DoSomething to public async Task DoSomething(), as I cannot change the IFoo interface, as that comes from a 3rd party library.

Stuart Grassie
  • 3,043
  • 1
  • 27
  • 36
  • No. You're stuck with `Task.Wait()`. If the interface can't change that's going to be a problem. Doing async over sync is generally a bad idea. – Yuval Itzchakov Jul 09 '15 at 09:17
  • 1
    Also, you really shouldn't be using `async void` like this, unless you don't care about the result of .`DoSomethingAsync` and [how](http://stackoverflow.com/questions/22369179/tap-global-exception-handler) exceptions are thrown inside `async void`. – noseratio Jul 09 '15 at 10:24

1 Answers1

2

The best answer, as other commenters have noted, is to not use async void. One of the main problems with async void is that there's no easy way to detect completion or retrieve results (success/exceptions) from an async void method. So, unit testing async void methods is a real pain.

Since you have an unchangeable IFoo interface and you need an asynchronous equivalent, consider adding an IFooAsync interface:

public interface IFooAsync : IFoo
{
  Task DoSomethingAsync();
}

Then always implement IFoo.DoSomething as an explicit implementation that just awaits DoSomethingAsync. This is similar to the approach I take for asynchronous ICommand implementations.

Your third-party library will only use the IFoo interface, while your unit tests (and other code) can use IFooAsync.

If, for some reason, this solution is unpalatable, you could unit test async void methods by explicitly giving them a context. I have an AsyncContext in my AsyncEx library that should work for this:

[Test]
public void FooTest()
{
  var foo = new Foo();

  Assert.That(AsyncContext.Run(() => foo.DoSomething()),
      Throws.InstanceOf<Exception>());
}

However, I recommend using the IFooAsync approach; I think it's a cleaner design anyway.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810