Tests unitaires et d’intégration dans les applications API minimales

Par Fiyaz Bin Hasan et Rick Anderson

Présentation des tests d’intégration

Les tests d’intégration évaluent les composants d’une application à un niveau plus large que les tests unitaires. Les tests unitaires sont utilisés pour tester des composants logiciels isolés, tels que des méthodes de classe individuelles. Les tests d’intégration confirment que deux ou plusieurs composants d’application fonctionnent ensemble pour produire un résultat attendu, y compris éventuellement chaque composant requis pour traiter entièrement une requête.

Ces tests plus larges sont utilisés pour tester l’infrastructure et la structure complète de l’application, y compris souvent les composants suivants :

  • Base de données
  • Système de fichiers
  • Appliances réseau
  • Pipeline requête-réponse

Les tests unitaires utilisent des composants fabriqués, connus comme des faux ou des objets fictifs, à la place des composants d’infrastructure.

En opposition aux tests unitaires, les tests d’intégration :

  • Utilisent les composants réels que l’application utilise en production.
  • Exigent davantage de code et de traitement des données.
  • Prennent plus de temps pour s’exécuter.

Par conséquent, limitez l’utilisation des tests d’intégration aux scénarios d’infrastructure les plus importants. Si un comportement peut être testé à l’aide d’un test unitaire ou d’un test d’intégration, choisissez le test unitaire.

Dans les discussions sur les tests d’intégration, le projet testé est fréquemment appelé système testé, ou « ST » pour faire court. « ST » est utilisé tout au long de cet article pour faire référence à l’application ASP.NET Core testée.

N’écrivez pas de tests d’intégration pour chaque permutation des données et de l’accès aux fichiers avec des bases de données et des systèmes de fichiers. Quel que soit le nombre d’emplacements d’une application qui interagissent avec des bases de données et des systèmes de fichiers, un ensemble ciblé de tests d’intégration de lecture, d’écriture, de mise à jour et de suppression est généralement capable de tester de manière adéquate les composants de base de données et de système de fichiers. Utilisez des tests unitaires pour les tests de routine de la logique de méthode qui interagissent avec ces composants. Dans les tests unitaires, l’utilisation d’infrastructures fausses ou fictives entraîne une exécution des tests plus rapide.

tests d’intégration ASP.NET Core

Les tests d’intégration dans ASP.NET Core nécessitent les éléments suivants :

  • Un projet de test est utilisé pour contenir et exécuter les tests. Le projet de test a une référence au ST.
  • Le projet de test crée un hôte web de test pour le ST et utilise un client de serveur de test pour gérer les demandes et les réponses avec le ST.
  • Un exécuteur de test est utilisé pour exécuter les tests et livrer les résultats des tests.

Les tests d’intégration suivent une séquence d’événements qui inclut les étapes de test habituelles Arrange, Act et Assert :

  1. L’hôte web du ST est configuré.
  2. Un client de serveur de test est créé pour envoyer des requêtes à l’application.
  3. L’étape de test Arrange est exécutée : l’application de test prépare une requête.
  4. L’étape de test Act est exécutée : le client envoie la requête et reçoit la réponse.
  5. L’étape de test Assert est exécutée : la réponse réelle est validée en tant que Réussite ou Échec en fonction d’une réponse attendue.
  6. Le processus se poursuit jusqu’à ce que tous les tests soient exécutés.
  7. Les résultats des tests sont livrés.

En règle générale, l’hôte web de test est configuré différemment de l’hôte web normal de l’application pour les séries de tests. Par exemple, une base de données différente ou des paramètres d’application différents peuvent être utilisés pour les tests.

Les composants d’infrastructure, tels que l’hôte web de test et le serveur de test en mémoire (TestServer), sont fournis ou gérés par le package Microsoft.AspNetCore.Mvc.Testing . L’utilisation de ce package simplifie la création et l’exécution des tests.

Le package Microsoft.AspNetCore.Mvc.Testing gère les tâches suivantes :

  • Copie le fichier de dépendances (.deps) du ST dans le répertoire du projet de test bin.
  • Définit la racine du contenu sur la racine du projet du ST afin que soient trouvés les pages/vues et fichiers statiques quand les tests sont exécutés.
  • Il fournit la classe WebApplicationFactory afin de simplifier l’amorçage de l’application testée avec TestServer.

La documentation sur les tests unitaires décrit comment configurer un projet de test et un exécuteur de tests, ainsi que des instructions détaillées sur l’exécution des tests et des recommandations pour nommer les tests et les classes de test.

Séparez les tests unitaires des tests d’intégration dans différents projets. Séparation des tests :

  • Permet de s’assurer que les composants de test d’infrastructure ne sont pas accidentellement inclus dans les tests unitaires.
  • Permet de contrôler quel ensemble de tests sont exécutés.

L’exemple de code sur GitHub fournit un exemple de tests unitaires et d’intégration sur une application API minimale.

Types d’implémentation IResult

Les types d’implémentation IResult publics dans l’espace de noms Microsoft.AspNetCore.Http.HttpResults peuvent être utilisés pour tester unitairement des gestionnaires de routes minimaux lors de l’utilisation de méthodes nommées au lieu de lambdas.

Le code suivant utilise la classe NotFound<TValue> :

[Fact]
public async Task GetTodoReturnsNotFoundIfNotExists()
{
    // Arrange
    await using var context = new MockDb().CreateDbContext();

    // Act
    var result = await TodoEndpointsV1.GetTodo(1, context);

    //Assert
    Assert.IsType<Results<Ok<Todo>, NotFound>>(result);

    var notFoundResult = (NotFound) result.Result;

    Assert.NotNull(notFoundResult);
}

Le code suivant utilise la classe Ok<TValue> :

[Fact]
public async Task GetTodoReturnsTodoFromDatabase()
{
    // Arrange
    await using var context = new MockDb().CreateDbContext();

    context.Todos.Add(new Todo
    {
        Id = 1,
        Title = "Test title",
        Description = "Test description",
        IsDone = false
    });

    await context.SaveChangesAsync();

    // Act
    var result = await TodoEndpointsV1.GetTodo(1, context);

    //Assert
    Assert.IsType<Results<Ok<Todo>, NotFound>>(result);

    var okResult = (Ok<Todo>)result.Result;

    Assert.NotNull(okResult.Value);
    Assert.Equal(1, okResult.Value.Id);
}

Ressources supplémentaires