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

时间:2016-10-10 10:26:43

标签: c# unit-testing asynchronous

这些是我的业务引擎的方法。 Upload正在调用内部异步方法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的委托中调用方法本身时,如何验证在模拟上调用方法?按时间mock.Verify被称为任务仍然没有执行。 任何人都可以提供一些解决方案,以便每次都能通过这个测试用例

2 个答案:

答案 0 :(得分:2)

正如其他人所说,最佳解决方案是让方法返回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 type来完成:

[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

答案 1 :(得分:1)

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

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

  

Async void方法很难测试。

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

  

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

如果您还不确定,here's another article包含其他一些原因可以避免async void

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

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

和测试...

[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);
}