Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Await a Async Void method call 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 unit test it 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 assert is called before I get done with the mocked up LookUpIdAsync. In my normal code, that is just what I want. But for my Unit test I don't want that.

I am converting to Async/Await from using BackgroundWorker. With background worker this was functioning correctly because I could wait for the BackgroundWorker to finish.

But there does not seem to be a way to wait for a async void method...

How can I unit test this method?

like image 264
Vaccano Avatar asked Jan 07 '13 22:01

Vaccano


People also ask

Can I unit test a void method?

In conclusion, there are multiple ways to test the void method, which is dependent on the method's side-effects and what kind of test you wish to run. A unit test checks the method's functionality and can cover the void method if the side effect is stored in publicly available property.

Can unit tests be async?

Async unit tests that return Task have none of the problems of async unit tests that return void. Async unit tests that return Task enjoy wide support from almost all unit test frameworks.

Can you call an async method without await?

In this way, an async function without an await expression will run synchronously. If there is an await expression inside the function body, however, the async function will always complete asynchronously. Code after each await expression can be thought of as existing in a .then callback.

What is async-await method?

The async keyword turns a method into an async method, which allows you to use the await keyword in its body. When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete. await can only be used inside an async method.


2 Answers

You should avoid async void. Only use async void for event handlers. DelegateCommand is (logically) an 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 it as:

[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 an async void method, you can do so with my 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 changes the SynchronizationContext for your view model during its lifetime.

like image 113
Stephen Cleary Avatar answered Sep 19 '22 12:09

Stephen Cleary


An async void method is essentially a "fire and forget" method. There is no means of getting back a completion event (without an external event, etc).

If you need to unit test this, I would recommend making it an async Task method instead. You can then call Wait() on the results, which will notify you when the method completes.

However, this test method as written would still not work, as you're not actually testing DoStuff directly, but rather testing a DelegateCommand which wraps it. You would need to test this method directly.

like image 38
Reed Copsey Avatar answered Sep 22 '22 12:09

Reed Copsey