J'ai une méthode qui ressemble à ceci:
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);
}
J'essaie de le tester un peu comme ceci:
[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();
}
Mon assertion est appelée avant que je finisse avec la maquette LookUpIdAsync. Dans mon code normal, c'est exactement ce que je veux. Mais pour mon test d'unité, je ne le veux pas.
Je suis en train de convertir en Async/Await en utilisant BackgroundWorker. Avec un arrière-plan, cela fonctionnait correctement car je pouvais attendre que BackgroundWorker se termine.
Mais il ne semble pas y avoir de moyen d’attendre une méthode vide async ...
Comment puis-je tester cette méthode?
J'ai trouvé un moyen de le faire pour les tests unitaires:
[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 clé ici est que, parce que je suis en train de tester les unités, je peux substituer à la tâche le retour de mon appel asynchrone (dans mon vide asynchrone). Je vérifie ensuite que la tâche est terminée avant de continuer.
Vous devriez éviter async void
. Utilisez uniquement async void
pour les gestionnaires d'événements. DelegateCommand
est (logiquement) un gestionnaire d'événements, vous pouvez donc le faire comme suit:
// 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);
}
et testez l'unité comme:
[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();
}
Ma réponse recommandée est ci-dessus. Mais si vous voulez vraiment tester une méthode async void
, vous pouvez le faire avec ma bibliothèque 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();
}
Mais cette solution modifie la SynchronizationContext
de votre modèle de vue pendant sa durée de vie.
Une méthode async void
est essentiellement une méthode "feu et oublie". Il n'y a aucun moyen de récupérer un événement d'achèvement (sans événement externe, etc.).
Si vous avez besoin de tester cela à l'unité, je vous conseillerais plutôt d'utiliser une méthode async Task
. Vous pouvez ensuite appeler Wait()
sur les résultats pour vous avertir de la fin de la méthode.
Cependant, cette méthode de test telle qu'elle est écrite ne fonctionnerait toujours pas, car vous ne testez pas DoStuff
directement, mais plutôt une DelegateCommand
qui l'encapsule. Vous auriez besoin de tester cette méthode directement.
Vous pouvez utiliser AutoResetEvent pour arrêter la méthode de test jusqu'à la fin de l'appel asynchrone:
[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);
}
La réponse fournie teste la commande et non la méthode async. Comme mentionné ci-dessus, vous aurez besoin d'un autre test pour tester cette méthode asynchrone.
Après avoir passé du temps avec un problème similaire, j'ai trouvé une attente facile pour tester une méthode asynchrone dans un test unitaire en appelant simplement de manière synchrone:
protected static void CallSync(Action target)
{
var task = new Task(target);
task.RunSynchronously();
}
et l'utilisation:
CallSync(() => myClass.MyAsyncMethod());
Le test attend sur cette ligne et continue une fois que le résultat est prêt afin que nous puissions affirmer immédiatement après.
Le seul moyen que je connaisse est de transformer votre méthode async void
en méthode async Task
J'ai eu un problème similaire. Dans mon cas, la solution consistait à utiliser Task.FromResult
dans la configuration du moq pour .Returns(...)
comme suit:
orderService.LookUpIdAsync(Arg.Any<long>())
.Returns(Task.FromResult(null));
Moq propose également une méthode ReturnsAysnc(...)
.
Changez votre méthode pour retourner une tâche et vous pouvez utiliser Task.Result
bool res = configuration.InitializeAsync(appConfig).Result;
Assert.IsTrue(res);