在单元测试用例中等待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的委托中调用时,我如何验证在模拟上调用了该方法?被时间嘲弄。验证称为任务仍未执行。谁能提供一些解决方案,使这个测试用例每次都能通过
正如其他人所指出的,最好的解决方案是使该方法返回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
。
单元测试的主要好处之一是它们可以帮助您设计更好的代码。大多数时候,当你在努力编写测试时,它告诉你你的代码需要改进,而不是你的测试。
在这种情况下,问题是使用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);
}