92

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?

1

11 Answers 11

79

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.

Sign up to request clarification or add additional context in comments.

4 Comments

This would work. But I would rather not have to have two methods for all my async voids. I figured a way to do it (at least in my case). See my answer on this question if you are interested.
Nice - you've created the C# predecessor of github.com/btford/zone.js ZoneJS.
What is the benefit of awaiting DoLookupCommandImpl(idToLookup) in DoStuff(long idToLookUp)? What if it would be called without awaiting?
@jacekbe: awaiting the task observes exceptions; if you call it without await, then any failures are silently ignored.
64

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.

6 Comments

I can't change it to return Task because the DelegateCommand will not allow that.
Having a unit test scaffolding around my code is very important. I may have to make all (important) "async void" methods use BackgroundWorker if they can't be unit tested.
@Vaccano The same thing would happen with BackgroundWorker - you just need to make this an async Task instead of async void, and wait on the task...
@Vaccano You should have no async void methods (except for event handlers). If an exception is raised in async void method, how are you going to handle it?
I figured out a way to make it work (at least for this case). See my answer on this question if you are interested.
|
18

I figured out a way to do it 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 here is that because I am unit testing I can substitute in the task I want to have my async call (inside my async void) to return. I then just make sure the task has completed before I move on.

2 Comments

Just because your lookupTask has finished, doesn't mean the method under test (DoStuff? or DoLookupCommand?) has finished running. There is a small chance that the task has finished running, but IsSearchShowing hasn't been set to false yet, in which case your assertion would fail.
An easy way to prove this would be to put Thread.Sleep(2000) before setting IsSearchShowing to false.
10

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

Comments

5

You can use an AutoResetEvent to halt the test 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); } 

2 Comments

any AsyncMethodToTest class sample ?
Why not just use Wait() ?
5

The provided answer tests the command and not the async method. As mentioned above you'll need another test to test that async method as well.

After spending some time with a similar problem i found an easy wait to test an async method in a unit test by just calling in synchronously:

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

and the usage:

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

The test waits on this line and continues after the result is ready so we can assert immediately afterwards.

1 Comment

Are you sure that RunSynchronously() can wait until async void has finished ?
2

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

bool res = configuration.InitializeAsync(appConfig).Result; Assert.IsTrue(res); 

2 Comments

what is configuration ?
@Dementic An example? Or more specific an instance of an object with a member InitializeAsync that returns a task.
0

I ended up with a different approach, something that was suggested in https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#avoid-async-void

All your async void method does is call this async Task method internally.

private async void DoStuff(long idToLookUp) { await DoStuffAsync(idLookUp).ConfigureAwait(false); } internal async Task DoStuffAsync(long idToLookUp) //Note: make sure to expose Internal to your test project { IOrder order = await orderService.LookUpIdAsync(idToLookUp); // Close the search IsSearchShowing = false; } 

And then, instead of calling the async void method in my test, I called the async Task method.

[TestMethod] public async Task TestDoStuff() { //+ Arrange //+ Act await myViewModel.DoStuffAsync(0); //+ Assert } 

Comments

0

For NUnit using the following worked.

[Test] pubic async Task TestSomeStuff() { var result = await CallTheAsyncFunction(); //Assert } 

Note that to test if the async method throws a particular exception, you don't need to write your test as asynchronous, but rather use the Assert.ThrowsAsync API like the below.

 [Test] public void Test_Async_Method_Throws_Exception() { var invalidJson = " } Totally Invalid ; Json String {"; Assert.ThrowsAsync<JsonReaderException>(() => JsonParser.Parse(invalidJson)); } 

Comments

-1

I had a similar issue. In my case, the solution was to use Task.FromResult in the moq setup for .Returns(...) like so:

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

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

Comments

-1

Sometimes Wait doesn't work with test cases when you run multiple individual task in your actual implementation.

The Code:

private async void DoStuff(long idToLookUp) { IOrder order = await orderService.LookUpIdAsync(idToLookUp).Wait(); IsSearchShowing = false; } 

1 Comment

This seems like a comment on other answers? It doesn’t seem like an answer. If I’m misunderstanding, please edit your answer to provide clarification.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.