在单元测试用例中等待void async方法完成

本文关键字:async 方法 void 等待 单元 测试用例 | 更新日期: 2023-09-27 18:12:34

这些是我的业务引擎的方法。上传正在调用内部异步方法UploadAsync()

public void Upload(Stream data)
{  
   //Some logic
   //Call private async method
   UploadAsync(data);
}
private async void UploadAsync(Object data)
{
    await Task.Run(() =>
    {
               using (var factory = new DataRepositoryFactoryObject<IAllCommandRepository>(DataRepositoryFactory))
               {
                   factory.Repository.UploadData(data);
               }
           }
    );
}

这是Upload()方法的单元测试用例

[TestMethod, TestCategory("Unit")]
public void Upload_ValidData_Success()
{
    //other logic to setup mock repository
    //call public method which calls async method
    engine.Upload(data);
   _mockAllCommandRepository.Verify(x => x.Upload(It.Is<Object>(t => t != null)), Times.Once);
}

这个测试用例偶尔在验证方法上失败,有以下例外:

016-10-06T19:25:20.4982657Z ##[error]Expected invocation on the mock once, but was 0 times: x =>  x.Upload(It.Is<Object>(t => t != null)), Times.Once);

根据我的分析,验证方法在调用异步方法之前被调用。这可能是这个测试用例失败的原因。当方法本身在传递给Task.Run的委托中调用时,我如何验证在模拟上调用了该方法?被时间嘲弄。验证称为任务仍未执行。谁能提供一些解决方案,使这个测试用例每次都能通过

在单元测试用例中等待void async方法完成

正如其他人所指出的,最好的解决方案是使该方法返回TaskTask -返回async方法更容易测试:

private async Task UploadAsync(Object data)
{
  await Task.Run(() =>
  {
    using (var factory = new DataRepositoryFactoryObject<IAllCommandRepository>(DataRepositoryFactory))
    {
      factory.Repository.UploadData(data);
    }
  });
}
private async void Upload(Object data)
{
  await UploadAsync(data);
}

那么你的单元测试可以是:

[TestMethod, TestCategory("Unit")]
public async Task Upload_ValidData_Success()
{
  //other logic to setup mock repository
  //call public method which calls async method
  await engine.UploadAsync(data);
  _mockAllCommandRepository.Verify(x => x.Upload(It.Is<Object>(t => t != null)), Times.Once);
}

但是,如果出于某种原因,您确实需要来测试async void方法,则可以使用我的AsyncContext类型:

[TestMethod, TestCategory("Unit")]
public void Upload_ValidData_Success()
{
  //other logic to setup mock repository
  //call public method which calls async method
  AsyncContext.Run(() => engine.Upload(data));
  _mockAllCommandRepository.Verify(x => x.Upload(It.Is<Object>(t => t != null)), Times.Once);
}

旁注:在理想的情况下,我建议将UploadDataAsync添加到存储库中,然后删除Task.Run

单元测试的主要好处之一是它们可以帮助您设计更好的代码。大多数时候,当你在努力编写测试时,它告诉你你的代码需要改进,而不是你的测试。

在这种情况下,问题是使用void返回类型和async方法,这是Stephen Cleary建议反对的。事实上,他在文章中引用的原因之一是:

Async void方法很难测试。

我认为他反对async void方法的最有说服力的论据之一是:

当返回类型为Task时,调用者知道它正在处理一个未来操作;当返回类型为void时,调用者可能认为方法在返回时已经完成。

如果你还不相信,这里有另一篇文章,其中包含了避免async void的其他几个原因。

总之,您应该更改您的方法以返回Task。然后你就可以在考试中等待了。

private async Task UploadAsync(Object data)
{
    return Task.Run(() => {
           using (var factory = new DataRepositoryFactoryObject<IAllCommandRepository>(DataRepositoryFactory))
           {
               factory.Repository.UploadData(data);
           }
    });
}

和test…

[TestMethod, TestCategory("Unit")]
public void Upload_ValidData_Success()
{
    //other logic to setup mock repository
    //call public method which calls async method
    engine.Upload(data).Wait();
   _mockAllCommandRepository.Verify(x => x.Upload(It.Is<Object>(t => t != null)), Times.Once);
}