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?
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.
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.
async Task
instead of async void
, and wait on the task...
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 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.
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.
Thread.Sleep(2000)
before setting IsSearchShowing
to false.
The only way I know is to turn your async void
method to async Task
method
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);
}
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.
Change your method to return a Task and you can use Task.Result
bool res = configuration.InitializeAsync(appConfig).Result;
Assert.IsTrue(res);
configuration
?
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.
Success story sharing
await
ing the task observes exceptions; if you call it withoutawait
, then any failures are silently ignored.