簡體   English   中英

等待在單元測試用例中完成void異步方法

[英]Wait for void async method to complete in unit test case

這些是我的業務引擎的方法。 上傳是在內部調用異步方法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被稱為任務仍然沒有執行。 任何人都可以提供一些解決方案,以便這個測試用例每次都會通過

正如其他人所說, 最好的解決方案是讓方法返回Task Task返回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

單元測試的主要好處之一是它們可以幫助您設計更好的代碼。 大多數情況下,當您努力編寫測試時,它告訴您需要改進代碼,而不是測試。

在這種情況下,問題是使用帶有async方法的void返回類型,這是Stephen Cleary 建議的 實際上,他在文章中引用的原因之一是:

異步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);
           }
    });
}

和測試......

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM