等待unit testing的Async Void方法调用

我有一个像这样的方法:

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

我想unit testing它是这样的:

 [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的模拟之前,我的断言被调用。 在我正常的代码中,这正是我想要的。 但是对于我的unit testing,我不想要那个。

我正在转换为使用BackgroundWorker的asynchronous/等待。 随着后台工作人员这是正常工作,因为我可以等待BackgroundWorker完成。

但似乎没有办法等待一个asynchronous无效的方法…

我怎样才能unit testing这个方法?

一个async void方法本质上是一个“忘却”的方法。 没有办法找回完成事件(没有外部事件等)。

如果你需要unit testing这个,我build议把它做成一个async Task方法。 然后你可以在结果上调用Wait() ,这个方法完成后会通知你。

但是,这种testing方法仍然不起作用,因为您并不直接testingDoStuff ,而是testing一个包装它的DelegateCommand 。 你需要直接testing这个方法。

你应该避免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); } 

并unit testing它:

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

我build议的答案在上面。 但是,如果你真的想testing一个async void方法,你可以用我的AsyncEx库来做到这一点 :

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

但是这个解决scheme在其生命周期中更改了视图模型的SynchronizationContext

我想出了一个办法来做unit testing:

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

这里的关键是,因为我是unit testing,我可以在任务中replace我想要我的asynchronous调用(在我的asynchronous无效)返回。 然后我才确定任务已经完成,然后再继续。

您可以使用AutoResetEvent在asynchronous调用完成之前暂停testing方法:

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

提供的答案testing命令而不是asynchronous方法。 如上所述,您还需要另一个testing来testingasynchronous方法。

花了一些时间与类似的问题后,我发现一个简单的等待testing一个unit testingasynchronous方法只需同步调用:

  protected static void CallSync(Action target) { var task = new Task(target); task.RunSynchronously(); } 

和用法:

 CallSync(() => myClass.MyAsyncMethod()); 

testing在这条线上等待,并在结果准备就绪后继续,以便我们可以在之后立即断言。

我知道的唯一方法是把你的“asynchronous无效”方法变成“asynchronous任务”方法