我有一个看起来像这样的方法:
private async void DoStuff(long idToLookUp)
{
IOrder order = await orderService.LookUpIdAsync(idToLookUp);
// Close the search
IsSearchShowing = false;
}
//Other stuff in case you want to see it
public DelegateCommand<long> DoLookupCommand{ get; set; }
ViewModel()
{
DoLookupCommand= new DelegateCommand<long>(DoStuff);
}
我正在尝试像这样对它进行单元测试:
[TestMethod]
public void TestDoStuff()
{
//+ Arrange
myViewModel.IsSearchShowing = true;
// container is my Unity container and it setup in the init method.
container.Resolve<IOrderService>().Returns(orderService);
orderService = Substitute.For<IOrderService>();
orderService.LookUpIdAsync(Arg.Any<long>())
.Returns(new Task<IOrder>(() => null));
//+ Act
myViewModel.DoLookupCommand.Execute(0);
//+ Assert
myViewModel.IsSearchShowing.Should().BeFalse();
}
在我完成模拟 LookUpIdAsync 之前调用了我的断言。在我的正常代码中,这正是我想要的。但是对于我的单元测试,我不希望那样。
我正在从使用 BackgroundWorker 转换为 Async/Await。使用后台工作人员,这可以正常工作,因为我可以等待 BackgroundWorker 完成。
但似乎没有办法等待 async void 方法......
如何对这种方法进行单元测试?
您应该避免使用 async void
。仅将 async void
用于事件处理程序。 DelegateCommand
是(逻辑上)一个事件处理程序,所以你可以这样做:
// Use [InternalsVisibleTo] to share internal methods with the unit test project.
internal async Task DoLookupCommandImpl(long idToLookUp)
{
IOrder order = await orderService.LookUpIdAsync(idToLookUp);
// Close the search
IsSearchShowing = false;
}
private async void DoStuff(long idToLookUp)
{
await DoLookupCommandImpl(idToLookup);
}
并将其单元测试为:
[TestMethod]
public async Task TestDoStuff()
{
//+ Arrange
myViewModel.IsSearchShowing = true;
// container is my Unity container and it setup in the init method.
container.Resolve<IOrderService>().Returns(orderService);
orderService = Substitute.For<IOrderService>();
orderService.LookUpIdAsync(Arg.Any<long>())
.Returns(new Task<IOrder>(() => null));
//+ Act
await myViewModel.DoLookupCommandImpl(0);
//+ Assert
myViewModel.IsSearchShowing.Should().BeFalse();
}
我推荐的答案在上面。但如果您真的想测试 async void
方法,您可以使用我的 AsyncEx library:
[TestMethod]
public void TestDoStuff()
{
AsyncContext.Run(() =>
{
//+ Arrange
myViewModel.IsSearchShowing = true;
// container is my Unity container and it setup in the init method.
container.Resolve<IOrderService>().Returns(orderService);
orderService = Substitute.For<IOrderService>();
orderService.LookUpIdAsync(Arg.Any<long>())
.Returns(new Task<IOrder>(() => null));
//+ Act
myViewModel.DoLookupCommand.Execute(0);
});
//+ Assert
myViewModel.IsSearchShowing.Should().BeFalse();
}
但此解决方案会在其生命周期内更改您的视图模型的 SynchronizationContext
。
async void
方法本质上是一种“即发即弃”的方法。没有办法取回完成事件(没有外部事件等)。
如果您需要对此进行单元测试,我建议您改为使用 async Task
方法。然后您可以对结果调用 Wait()
,它会在方法完成时通知您。
但是,这种编写的测试方法仍然不起作用,因为您实际上并不是直接测试 DoStuff
,而是测试包装它的 DelegateCommand
。您需要直接测试此方法。
async Task
而不是 async void
,然后等待任务...
async void
方法(事件处理程序除外)。如果在 async void
方法中引发异常,您将如何处理它?
我想出了一种方法来进行单元测试:
[TestMethod]
public void TestDoStuff()
{
//+ Arrange
myViewModel.IsSearchShowing = true;
// container is my Unity container and it setup in the init method.
container.Resolve<IOrderService>().Returns(orderService);
orderService = Substitute.For<IOrderService>();
var lookupTask = Task<IOrder>.Factory.StartNew(() =>
{
return new Order();
});
orderService.LookUpIdAsync(Arg.Any<long>()).Returns(lookupTask);
//+ Act
myViewModel.DoLookupCommand.Execute(0);
lookupTask.Wait();
//+ Assert
myViewModel.IsSearchShowing.Should().BeFalse();
}
这里的关键是,因为我是单元测试,所以我可以在我想让我的异步调用(在我的 async void 内)返回的任务中替换。然后,我只需确保任务已完成,然后再继续。
lookupTask
已完成,并不意味着正在测试的方法(DoStuff
? 或 DoLookupCommand
?)已完成运行。任务完成运行但 IsSearchShowing
尚未设置为 false 的可能性很小,在这种情况下,您的断言将失败。
IsSearchShowing
设置为 false 之前放置 Thread.Sleep(2000)
。
我知道的唯一方法是将您的 async void
方法转换为 async Task
方法
您可以使用 AutoResetEvent 暂停测试方法,直到异步调用完成:
[TestMethod()]
public void Async_Test()
{
TypeToTest target = new TypeToTest();
AutoResetEvent AsyncCallComplete = new AutoResetEvent(false);
SuccessResponse SuccessResult = null;
Exception FailureResult = null;
target.AsyncMethodToTest(
(SuccessResponse response) =>
{
SuccessResult = response;
AsyncCallComplete.Set();
},
(Exception ex) =>
{
FailureResult = ex;
AsyncCallComplete.Set();
}
);
// Wait until either async results signal completion.
AsyncCallComplete.WaitOne();
Assert.AreEqual(null, FailureResult);
}
提供的答案测试命令而不是异步方法。如上所述,您还需要另一个测试来测试该异步方法。
在花了一些时间解决类似问题后,我发现只需同步调用即可在单元测试中测试异步方法:
protected static void CallSync(Action target)
{
var task = new Task(target);
task.RunSynchronously();
}
和用法:
CallSync(() => myClass.MyAsyncMethod());
测试在这一行等待并在结果准备好后继续,因此我们可以在之后立即断言。
更改您的方法以返回任务,您可以使用 Task.Result
bool res = configuration.InitializeAsync(appConfig).Result;
Assert.IsTrue(res);
configuration
是什么?
我有一个类似的问题。就我而言,解决方案是在 .Returns(...)
的 moq 设置中使用 Task.FromResult
,如下所示:
orderService.LookUpIdAsync(Arg.Any<long>())
.Returns(Task.FromResult(null));
或者,Moq 也有一个 ReturnsAysnc(...)
方法。
不定期副业成功案例分享
await
让任务观察异常;如果你在没有await
的情况下调用它,那么任何失败都会被忽略。