Attendre une méthode Async Void pour tester l’unité

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 DoLookupCommand{ get; set; } ViewModel() { DoLookupCommand= new DelegateCommand(DoStuff); } 

J’essaye de le tester comme ceci:

 [TestMethod] public void TestDoStuff() { //+ Arrange myViewModel.IsSearchShowing = true; // container is my Unity container and it setup in the init method. container.Resolve().Returns(orderService); orderService = Substitute.For(); orderService.LookUpIdAsync(Arg.Any()) .Returns(new Task(() => null)); //+ Act myViewModel.DoLookupCommand.Execute(0); //+ Assert myViewModel.IsSearchShowing.Should().BeFalse(); } 

Mon assert est appelé avant de terminer ma recherche avec LookUpIdAsync. Dans mon code normal, c’est exactement ce que je veux. Mais pour mon test unitaire, je ne le veux pas.

Je suis en train de convertir en Async / Await d’utiliser BackgroundWorker. Avec le travailleur en arrière-plan, cela fonctionnait correctement car je pouvais attendre la fin de BackgroundWorker.

Mais il ne semble pas y avoir de moyen d’attendre une méthode asynchrone vide …

Comment puis-je tester cette méthode?

Vous devriez éviter l’ 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 ceci:

 // 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 l’unité le teste comme:

 [TestMethod] public async Task TestDoStuff() { //+ Arrange myViewModel.IsSearchShowing = true; // container is my Unity container and it setup in the init method. container.Resolve().Returns(orderService); orderService = Substitute.For(); orderService.LookUpIdAsync(Arg.Any()) .Returns(new Task(() => 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().Returns(orderService); orderService = Substitute.For(); orderService.LookUpIdAsync(Arg.Any()) .Returns(new Task(() => null)); //+ Act myViewModel.DoLookupCommand.Execute(0); }); //+ Assert myViewModel.IsSearchShowing.Should().BeFalse(); } 

Mais cette solution modifie le SynchronizationContext pour votre modèle de vue au cours de sa durée de vie.

Une méthode de async void est essentiellement une méthode “fire and forget”. Il n’y a aucun moyen de récupérer un événement d’achèvement (sans événement externe, etc.).

Si vous devez tester cette unité, je vous recommande d’en faire une méthode de async Task . Vous pouvez ensuite appeler Wait() sur les résultats, ce qui vous avertira lorsque la méthode sera terminée.

Cependant, cette méthode de test telle que écrite ne fonctionnerait toujours pas, car vous ne testez DoStuff directement DoStuff , mais plutôt que vous testez un DelegateCommand qui l’enveloppe. Vous devrez tester cette méthode directement.

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().Returns(orderService); orderService = Substitute.For(); var lookupTask = Task.Factory.StartNew(() => { return new Order(); }); orderService.LookUpIdAsync(Arg.Any()).Returns(lookupTask); //+ Act myViewModel.DoLookupCommand.Execute(0); lookupTask.Wait(); //+ Assert myViewModel.IsSearchShowing.Should().BeFalse(); } 

La clé ici est que, parce que je suis en test unitaire, je peux me substituer à la tâche pour laquelle je souhaite que mon appel asynchrone (dans mon async annulé) soit renvoyé. Je m’assure alors que la tâche est terminée avant de continuer.

Vous pouvez utiliser un 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 asynchrone. Comme mentionné ci-dessus, vous aurez également 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 après que le résultat est prêt pour que nous puissions affirmer immédiatement après.

La seule façon dont je sais est de transformer votre méthode “aync void” en méthode “async Task”