Solución:
Tú deberías evitar async void
. Uso único async void
para controladores de eventos. DelegateCommand
es (lógicamente) un controlador de eventos, por lo que puede hacerlo así:
// 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);
}
y prueba unitaria como:
[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();
}
Mi respuesta recomendada está arriba. Pero si realmente quieres probar un async void
método, puede hacerlo con mi biblioteca 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();
}
Pero esta solución cambia el SynchronizationContext
para su modelo de vista durante su vida útil.
Un async void
El método es esencialmente un método de “disparar y olvidar”. No hay forma de recuperar un evento de finalización (sin un evento externo, etc.).
Si necesita realizar una prueba unitaria de esto, le recomendaría convertirlo en un async Task
método en su lugar. Entonces puedes llamar Wait()
en los resultados, que le notificarán cuando se complete el método.
Sin embargo, este método de prueba tal como está escrito aún no funcionaría, ya que en realidad no está probando DoStuff
directamente, sino probando un DelegateCommand
que lo envuelve. Debería probar este método directamente.
Descubrí una manera de hacerlo para las pruebas unitarias:
[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();
}
La clave aquí es que, debido a que estoy realizando pruebas unitarias, puedo sustituir en la tarea que quiero que vuelva mi llamada asíncrona (dentro de mi vacío asíncrono). Luego, solo me aseguro de que la tarea se haya completado antes de continuar.