Waiting for a call to the Async Void method for unit testing

I have a method that looks like this:

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

I am trying to execute unit test like this:

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

My statement is called before I finish with the mocked LookUpIdAsync. In my normal code, this is what I want. But for my unit test, I don't want this.

I am switching to Async / Await using BackgroundWorker. This worked correctly with the background worker, because I could wait for BackgroundWorker to complete.

But there seems to be no way to wait for the async void method ...

How can I unit test use this method?

+62
c # unit-testing async-await
Jan 07 '13 at 22:57
source share
8 answers

I figured out a way to do this for 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(); } 

The key point here is that since I am unit testing, I can replace in the task, I want my asynchronous call (inside my asynchronous void) to return. Then I just complete the task before moving on.

+17
Jan 08 '13 at 17:06
source share

You should avoid async void . Use only async void for event handlers. DelegateCommand (logically) event handler, so you can do it like this:

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

and unit test is like:

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



My recommended answer is above. But if you really want to test the async void method, you can do this using the 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(); } 

But this solution modifies the SynchronizationContext for your view model throughout its life.

+56
Jan 08 '13 at 3:05
source share

The async void method is essentially a fire and oblivion method. There is no means to return the completion event (without an external event, etc.).

If you need unit test, I would recommend using the async Task method instead. You can then call Wait() on results that will notify you of the completion of the method.

However, this test method, as written, will still not work, since you are not actually testing DoStuff directly, but rather checking out the DelegateCommand that wraps it. You will need to test this method directly.

+50
Jan 07 '13 at 23:09
source share

You can use AutoResetEvent to stop the testing method until the async call completes:

 [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); } 
+5
Oct 11 '13 at 7:38
source share

The only way I know is to turn your async void method into async Task method

+5
May 18 '17 at 9:43 a.m.
source share

The provided answer validates the command, not the async method. As mentioned above, you will need another test to test this asynchronous method.

After spending some time with a similar problem, I found an easy wait to test the asynchronization method in unit test, just by calling synchronously:

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

and use:

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

The test expects this line and continues after the result is ready, so we can immediately assert.

+4
Apr 10 '15 at 14:29
source share

Change your method to return Tasks, and you can use Task.Result

 bool res = configuration.InitializeAsync(appConfig).Result; Assert.IsTrue(res); 
0
Jan 19 '19 at 14:25
source share

I had a similar problem. In my case, the solution was to use Task.FromResult in setting up moq for .Returns(...) as follows:

 orderService.LookUpIdAsync(Arg.Any<long>()) .Returns(Task.FromResult(null)); 

Alternatively, Moq also has a ReturnsAysnc(...) method.

0
Aug 27 '19 at 20:26
source share



All Articles