Blazor : mener des tests unitaires avec bUnit

Faire des tests unitaires dans un projet informatique est important. L’arrivée de Blazor ne déroge pas à la règle.

Bien que relativement récente, la communauté autour de Blazor est active et permet l'émergence de projets facilitant la vie des développeurs. C’est en cherchant dans l’immense réserve des paquets NuGet que j’ai trouvé bUnit qui permet de réaliser des tests unitaires sur les composants Blazor. Ce projet vient par ailleurs d’être mis en avant par les équipes de Microsoft. Dans cet article, je vous présente cet outil qui vous permettra de tester vos composants.

bUnit

Qu’est-ce que bUnit ?

Blazor fonctionne avec des composants qui héritent de Icomposant ou componentBase. Ces composants sont managés par le runtime C#. Ce runtime gère la durée de vie des composants, l’injection de dépendance ou encore l’arbre de rendus. Ensuite l’interface utilisateur crée le DOM, écoute les actions de l’utilisateur, les évènements du navigateur, etc. Le but de bUnit, est de simuler cette gestion de l’interface utilisateur.

bUnit met en place un contexte de test permettant d’installer les dépendances comme IJsInterop, dataservice. Il permet également de simuler un rendu des composants avant d'y accéder et de les manipuler. Il crée également l’arbre DOM. Enfin, il déclenche les évènements. bUnit envoie les commandes au runtime C#, puis le runtime renvoie l’arbre de rendu mis à jour au contexte de test afin de mettre à jour le DOM. Ne s’exécutant pas dans un navigateur, bUnit permet également de mocker le JsInterop.

Installation

Pour utiliser bUnit, il faut d’abord créer un projet de tests. Il fonctionne aussi bien avec xUnit, Nuit ou MSTest. J’utilise pour ma part xUnit, mais libre à vous de choisir celui qui vous convient le mieux. Ajoutez ensuite le package Nuget correspondant à bUnit.

Attention, bUnit est toujours en preview, pensez à cocher la case « Inclure la version préliminaire » pour pouvoir le choisir en utilisant l’interface graphique.

 

 

 

Il faut ensuite ajouter le Nuget bunit.  

 

 

 

Ou via la console :

dotnet add package bunit --version 1.0.0-preview-01

Tests

Premier test

Afin d’illustrer le principe du test avec bUnit, je vais commencer par tester le composant compteur que l’on trouve à la création de chaque application Blazor par défaut. Le code est très simple :

 

 

 

Ici, on incrémente simplement le compteur de la page à chaque fois que l’on clique sur le bouton. Passons à la partie test. Dans le projet xUnit, je crée une nouvelle classe de test que je fais hériter de TestContext. Il s’agit d’une classe de bUnit permettant de créer des composants de test. public class BUnitTests : TestContext

Si vous ne souhaitez pas faire hériter votre classe, il est possible d’instancier TestContext directement dans le test. Ensuite, dans notre méthode de test, on crée le composant que l’on souhaite tester.

 

 

 

Si vous avez choisi de ne pas faire hériter la classe de TestContext, il faudra alors changer légèrement l’implémentation en remplaçant var cut = RenderComponent<Counter>(); par : ctx = new TestContext() ; var cut = ctx.RenderComponent<HelloWorld>();

On peut maintenant tester l’affichage par défaut de la page. Pour cela, on utilise MarkupMatches. On passe en paramètre le rendu espéré pour la page. Lors du premier affichage, on souhaite que la page du compteur affiche un décompte à 0.

 

 

 

Autrement dit le HTML suivant :

<h1>Counter</h1> <p>Current count: 0</p> <button class=""btn btn-primary"">Click me</button>

Ce qui se traduit pour notre test par ceci :

 

 

 

Il est désormais possible d’exécuter le test.

 

 

 

Et on constate qu’il passe avec succès !   L’étape suivante consiste à tester que le clic sur le bouton va bien incrémenter notre compteur de 1. On utilise dès lors l’attribut Find sur le composant afin de retrouver le bouton : cut.Find("button").

Puis on simule le clic : cut.Find("button").Click(); Cette manipulation simule le clic sur le bouton depuis l’interface. On teste ensuite que le compteur est bien incrémenté de 1.

 

 

 

Puis on peut exécuter à nouveau les tests.

 

 

 

Ces derniers se passent avec succès. On sait donc que le clic sur le bouton incrémente bien le compteur de 1. N.B.

Dans le MarkupMatches, l’ordre des attributs d’un élément n’est pas important. Dans l'exemple du bouton, si on avait un id en plus, on pourrait indifféremment écrire

<button class=""btn btn-primary"" id=""unid"">Click me</button> ou <button id=""unid"" class=""btn btn-primary"">Click me</button>.

Paramètres

Il est également possible de tester les paramètres. Reprenons l’exemple précédent et ajoutons un paramètre.

 

 

 

On ajoute donc un paramètre Increment qui servira à définir le pas d’incrément lors du clic sur le bouton. Le test reprend quasiment la même syntaxe. 

On définit la valeur du paramètre en utilisant la méthode SetParametersAndRender.

 

 

 

La valeur du paramètre est de 3, nous attendons une valeur égale pour le rendu.  

Événements asynchrones

  bUnit permet également de gérer les évènements asynchrones. Modifions notre composant counter afin d’ajouter une petite touche d’asynchronisme.

 

 

 

La méthode d’incrément est modifiée afin d’ajouter un délai avant l’incrémentation. Dans le test, il faut maintenant utiliser la méthode WaitForAssertion.

De cette manière, le test attend que l’évènement soit terminé avant de commencer.

 

 

 

La méthode possède un timeout par défaut de 1 seconde. Il est possible de définir une valeur différente en passant un Timespan en seconde au second argument.  

IJSRuntime

  Il est possible de simuler le JSInterop dans les tests bUnit. Reprenons l’exemple du compteur et modifions légèrement le code afin de faire un appel js.

 

 

 

Le GetPageTitle se contente de renvoyer le titre de la page. Du côté des tests, il faut également faire quelques ajustements. JSInterop au sein de bUnit fonctionne avec 2 modes, strict ou loose. Le mode loose configure l’implémentation pour retourner une valeur par défaut lorsqu’il reçoit un appel qui n’a pas été explicitement initialisé.

Le mode strict à l’inverse déclenche une exception si l’implémentation n’a pas été initialisée. Par défaut, c’est le mode strict qui est utilisé. Aussi, dans le cas du test du compteur ou l’appel js n’a pas d’importance, il faut déclarer JSInterop.Mode = JSRuntimeMode.Loose; dans le test pour qu’il fonctionne sans erreur.

 

 

 

Si cependant on veut tester l’appel JS, il faut alors l’initialiser avec la valeur de retour.

 

 

 

  On peut également vérifier que l’appel JS est bien invoqué en utilisant la méthode VerifyInvoke.

 

 

 

On veut ici vérifier que l’alerte est bien exécutée avec la méthode VerifyInvoke. Le second paramètre permet de vérifier que la méthode n’est appelée qu’une seule fois.

 

 

 

 

Injection de service

TestContext contient une propriété appelée Services. Cette propriété fonctionne de la même manière que celle que l’on retrouve dans le fichier Startup.cs. Il suffit donc de déclarer de la même manière, les composants que l’on souhaite ajouter en faisant simplement par exemple : Services.AddSingleton< IThemeService>(); Afin de tester l’injection de dépendance, j’ai créé une classe Theme.cs :

 

 

 

  Ainsi qu’une interface IThemeService :

 

 

 

Dont l’implémentation concrète se trouve dans la classe ThemeService.

 

 

 

Ce service renvoie un texte aléatoire du tableau ButtonText. J’ai également modifié le composant counter afin que le texte du bouton soit défini par le service.

 

 

 

Il est ensuite possible dans le test d’injecter le service et d’utiliser un moq pour le rendu.

 

 

 

Conclusion

Vous connaissez maintenant un outil permettant de tester vos composants Blazor. Il est bien sûr possible d’aller bien plus loin dans les tests, avec par exemple une sélection plus précise des éléments à tester dans la page, en faisant un usage pertinent des méthodes Find, FindAll.

Aussi je vous invite à consulter le site de bUnit pour en apprendre davantage. Et bien évidemment la pratique reste le meilleur moyen de vous familiariser avec ce formidable outil de test !