对同步方法调用异步方法的nunit async / await assertion

时间:2015-07-09 09:15:36

标签: c# asynchronous async-await nunit

此(n)单元测试失败:

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

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

这是受测试的课程:

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

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

如果我更改DoSomething这个:

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

然后测试将通过。我有什么方法可以避免这样做吗?

注意:我无法DoSomething的签名更改为public async Task DoSomething(),因为我无法更改IFoo界面,因为它来自第三方库

1 个答案:

答案 0 :(得分:2)

正如其他评论者所指出的那样,最佳答案是不使用async voidasync void的一个主要问题是,从async void方法检测完成或检索结果(成功/例外)并不容易。因此,单元测试async void方法真是一种痛苦。

由于您有一个不可更改的IFoo接口,并且您需要异步等效接口,请考虑添加IFooAsync接口:

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

然后始终将IFoo.DoSomething实现为只等待DoSomethingAsync的显式实现。这类似于我对asynchronous ICommand implementations采取的方法。

您的第三方库仅使用IFoo界面,而您的单元测试(和其他代码)可以使用IFooAsync

如果由于某种原因,这个解决方案不合适,那么可以通过明确地为它们提供上下文来单元测试async void方法。我有一个AsyncContext in my AsyncEx library应该适用于此:

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

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

但是,我建议使用IFooAsync方法;我认为无论如何它都是一个更清洁的设计。