等待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任务”方法