Tutoriel : Tester son application .NET 6 avec XUnit et EF InMemoryDatabase

Sengdao
27 mars 2024
Nous allons démystifier les tests unitaires et vous donner un guide pour en implémenter en .NET 6 avec XUnit et Entity Framework.

Introduction

Les tests unitaires… on vous en parle, on vous demande si vous en faites pendant vos entretiens, mais finalement on reste très théorique dans ces échanges. Du coup, nous allons démystifier les tests unitaires et vous donner un guide pour en implémenter en .NET 6 avec XUnit et Entity Framework.

Les Tests Unitaires (TU) c’est quoi ?

Scénario de tous les jours, un bug remonte dans le tableau de suivi des tâches sur votre projet :  

[QA] La fonction d’ajout de tâche dans la todo list ne fonctionne plus

Depuis la mise à jour de l’environnement de test le XX/XX/XXXX, la fonctionnalité d’ajout d’une tâche ne fonctionne plus.

Repro steps :

Faire la saisie d’une nouvelle tâche.

La popup affiche « Tâche ajoutée » en vert mais elle n’apparaît pas dans la liste.

Vous regardez les logs, rien d’alarmant, donc du coup vous devez rentrer dans le code et faire du debug. Au bout de 10 minutes vous constatez qu’une ligne a été commentée : « dbContext.Add(newTodoItem) ; »

Superbe régression !

Eh bien les tests unitaires, c’est LA pratique qui va permettre d’éviter les situations de ce genre.

Pourquoi tester ?

Les TU répondent à plusieurs problématiques très simple :

  • Comment fiabiliser notre code / nos applications
  • Comment avoir la garantie que le code que l’on souhaite déployer ne cause pas de régression ?

Quoi tester ?

Pour répondre à ces problématiques, c’est très simple, on va tester un maximum de cas possibles et pertinents pour notre contexte.

Dans notre scénario, nous pouvons par exemple tester la méthode « AddTodoItem » où le code causant la régression a été trouvé. Nous pouvons tester plusieurs cas, en sachant que plus le nombre de cas couverts est important, mieux c’est.

Ici, nous pouvons identifier les cas suivants :

En prenant en considération qu’il y a une base de données

  • Ajout en échec
    • Tâche existante avec les mêmes infos => AlreadyExistingException
    • Entrée invalide, donnée manquante => une exception
  • Ajout réussi
    • La méthode renvoie l’objet sauvegardé avec son ID autogénéré

Et Comment ?

Pour rédiger nos TU et ensuite les exécuter en .NET, nous utilisons surtout des bibliothèques comme XUnit et Moq pour faire des tests, en utilisant le principe Arrange-Act-Assert (« 3A »).

Il existe d’autres bibliothèques comme NUnit mais ce n’est pas celle sur laquelle nous ferons le focus aujourd’hui.

Pour rappel, 3A est un pattern assez populaire utilisé pour les TU

  • Arrange

Durant cette étape on va mettre en place les objets/données que l’on va manipuler. Cela peut être des mocks, des fakes, voire de vraies données.

  • Act

On exécute l’action que l’on veut effectuer sur nos objets/données. De manière générale on va exécuter une méthode (qui nécessite ou pas de paramètres).

  • Assert

C’est ici que l’on fait les vérifications pour notre test en comparant le résultat obtenu suite à l’action avec le résultat attendu.

Bonus

Les principes SOLID, vous les connaissez ? En faisant des TU, sous conditions que votre implémentation permette d’en faire, vous pouvez être sûr que vous respectez au moins 5 de ces principes. Oui, 5 sur 5.

Maintenant que le scénario est établi, allons-y !

Tutoriel  

Tout d’abord on a besoin d’un projet d’API web en .NET qui communique avec une base de données SQL Server via Entity Framework.

Grosso modo voilà la structure du projet que l’on utilise :

capture d'écran

Atawiz.UnitTestDemo.Core

  • Dtos : contient les DTOs, objets d’entrée de nos services
  • Exceptions : des exceptions spécifiques ou génériques
  • Models : les classes auxquelles sont mappées les tables de la BDD
  • Repositories : Interfaces des repositories pour manipuler les données
  • Services : Interfaces des services qui vont permettre de mapper les DTO vers les classes du dossier Models

Atawiz.UnitTestDemo.API

  • Controllers : nos contrôleurs API
  • Services : les implémentations des interfaces définies dans Core
  • Program.cs : Injection des dépendances & déclaration des middlewares

Atawiz.UnitTestDemo.EF

  • Context : DbContext avec les DbSet & le mapping (DB First)
  • DependencyInjection : méthodes d’extensions de ServiceCollection pour l’injection de dépendances
  • Repositories : Implémentation des interfaces de Repository définies dans Core

Atawiz.UnitTestDemo.Tests

  • C’est ici qu’on va mettre nos classes de tests

On va rédiger les tests pour la méthode CreateAsync(TodoItemDto) de l’interface ITodoItemService et son implémentation.

capture d'écran

Celle-ci est assez simple, si on ne trouve pas d’objet TodoItem en base ayant le même Title et le même Assignee, on ajoute l’objet en base, puis on le renvoie avec l’Id généré.

Si l’objet existe déjà, on throw une AlreadyExistingException indiquant que l’objet existe déjà.

Pour tester le comportement de la base de données, on va donc commencer par mettre en place un DbContext en mémoire dans lequel on va insérer nos données de test (3 TodoItem).

Ensuite on va l’injecter dans notre repository, que l’on injectera à son tour dans le service.

capture d'écran

Pour les méthodes de tests, on va utiliser l’attribut [Fact] pour les déclarer comme étant des Tests.

On va diviser le code dans chaque méthode en 3 parties :

  • Arrange : on prépare les inputs

Dans notre cas on va juste préparer un dto qu’on va passer en paramètre à notre méthode CreateAsync()

  • Act : on exécute le comportement à tester

On appelle la méthode CreateAsync() avec notre input

  • Assert : on compare avec le résultat attendu

Selon ce qu’on souhaite tester on va faire des Assert d’égalité, sur des types ou des nullable.

Maintenant, notre premier scénario : « Quand l’ajout a réussi, l’objet ajouté doit être renvoyé ».

Pour tester ce scénario, on peut créer plusieurs tests :

  • Un test pour vérifier qu’on retourne un objet en sortie de l’appel de la méthode CreateAsync

capture d'écran  

  • Un test pour vérifier que l’objet retourné est le même qu’en entrée.

capture d'écran

  • Un test pour vérifier que notre base de données contient bien 4 éléments après l’ajout.

capture d'écran

  • Un test pour vérifier que l’Id de notre nouvel objet est supérieur au plus grand Id en base.

capture d'écran

Pour les cas d’échec, on va tester sur les types d’exceptions renvoyés :

  • Un test pour vérifier qu’une exception est renvoyée si l’objet en entrée a des propriétés manquantes

capture d'écran

  • Un test pour vérifier qu’une exception est renvoyée si l’objet existe déjà en base en se basant sur une règle simple

capture d'écran

Pour exécuter les tests, ouvrez l’Explorateur de tests et cliquez sur « Exécuter ».

capture d'écran

Une fois les tests exécutés les résultats sont affichés. En cas de réussite les tests seront précédés d’une icône verte.

capture d'écran  

En cas d’échec vous aurez une icône rouge.

capture d'écran  

Conclusion

Vous savez maintenant comment faire des TU avec XUnit et EF InMemoryDatabase. Comme vous pouvez le voir ça n’a rien de bien compliqué et ce n’est pas très chronophage pour un gain énorme en lisibilité et fiabilité de votre code.

Articles similaires

Aucun article

career block background image

Vous souhaitez nous rejoindre ?

Rendez-vous sur notre site carrière pour accéder à toutes nos offres

Microsoft Partner
Databricks Partner
Great Place To Work
Microsoft Partner
Databricks Partner
Great Place To Work
Microsoft Partner
Databricks Partner
Great Place To Work
Microsoft Partner
Databricks Partner
Great Place To Work