nunit async/await 断言同步方法调用异步方法

nunit async/await assertion on synchronous method calling async method

这 (n) 个单元测试失败:

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

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

这是正在测试的 class:

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接口,因为那样来自第三方库。

正如其他评论者指出的那样,最好的答案是不要使用 async voidasync void 的主要问题之一是没有简单的方法来检测完成或从 async void 方法检索结果 (success/exceptions)。所以,单元测试 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方法;无论如何,我认为这是一个更简洁的设计。