Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Semaine 7

Cette semaine, nous allons nous concentrer sur la qualité et la robustesse de notre application. Nous allons introduire deux concepts fondamentaux pour tout développement professionnel : les Tests Unitaires et le Logging.

Objectifs

  1. Logging : Mettre en place un système de journalisation pour suivre l’activité de l’application et diagnostiquer les problèmes.
  2. Tests Unitaires : Créer un projet de test et écrire nos premiers tests pour valider la logique métier de nos services.

Partie 1 : Le Logging

Le logging (journalisation) est essentiel pour comprendre ce qui se passe dans votre application une fois qu’elle est déployée. C’est votre “boîte noire”.

1. Le concept

En .NET, le logging est intégré nativement via l’interface ILogger<T>. Il existe plusieurs niveaux de logs :

  • Trace / Debug : Pour le développement, très verbeux.
  • Information : Flux normal de l’application (ex: “Utilisateur connecté”).
  • Warning : Quelque chose d’inattendu mais pas bloquant (ex: “Tentative de connexion échouée”).
  • Error : Erreur bloquante ou exception gérée (ex: “Erreur lors du paiement”).
  • Critical : Crash de l’application.

2. Injecter le Logger

Nous allons ajouter des logs dans notre application.

Consignes

Utilisez l’injection de dépendance pour injecter ILogger<MaClasse> dans vos services, middlewares et contrôleurs.

3. Ajouter des Logs

Ajoutez ensuite du logging dans vos méthodes.

  • Logger les erreurs en LogError dans votre middleware ErrorHandlingMiddleware.
  • Ajoutez du logs dans vos services pour tracer les actions importantes (Achat d’item, création de compte, Click, etc.).
  • Ajoutez du logs dans vos contrôleurs pour remonter les erreurs et les actions importantes si applicable.
  • Ajoutez un middleware qui log en Debug les requêtes et les réponses faites à votre API.
  • Dans le Program.cs, ajoutez un logging pour indiquer que l’application est en cours de démarrage et que l’initialisation est terminée.

Note : Pour le logging dans le Program.cs, vous pouvez utiliser le logger injecté dans la variable app.Logger. Example: app.Logger.LogInformation("Application is starting up...");

Note : Utilisez les {} pour passer des paramètres au message de log (Structured Logging). Ne faites pas de concaténation de chaînes ("User " + username). Example: _logger.LogInformation("User {Username} logged in", username);

4. Astuce : Trop de logs ?

Par défaut, Entity Framework affiche les requêtes SQL exécutées, ce qui peut polluer votre console. Pour réduire le bruit, vous pouvez modifier le fichier appsettings.json pour augmenter le niveau minimum de log requis pour EF Core.

Dans appsettings.json :

"Logging": {
  "LogLevel": {
    "Default": "Information",
    "Microsoft.AspNetCore": "Warning",
    "Microsoft.EntityFrameworkCore": "Warning" // N'affiche que les warnings et erreurs de EF
  }
}

Partie 2 : Tests Unitaires

Les tests unitaires servent à vérifier qu’une petite partie de code (une “unité”, souvent une méthode) fonctionne comme prévu, indépendamment du reste du système (base de données, réseau, etc.).

1. Création du projet de test

Nous allons utiliser xUnit, le framework de test standard en .NET.

Consignes

  1. À la racine de votre solution (dossier parent de GameServerApi), créez un nouveau projet de test :
    dotnet new xunit -o GameServerApi.Tests
    
  2. Ajoutez le projet à la solution :
    dotnet sln add GameServerApi.Tests
    
  3. Ajoutez une référence vers votre projet principal :
    cd GameServerApi.Tests
    dotnet add reference ../GameServerApi/GameServerApi.csproj
    

2. Les Outils de Mocking

Pour tester nos services sans utiliser une vraie base de données nous avons besoin de simuler (“mocker”) le comportement de nos dépendances.

Nous allons utiliser deux bibliothèques :

  • Moq : Pour créer des faux objets (mocking de services).
  • Microsoft.EntityFrameworkCore.InMemory : Pour simuler la base de données en mémoire.

Installez ces packages dans le projet GameServerApi.Tests :

dotnet add package Moq
dotnet add package Microsoft.EntityFrameworkCore.InMemory --version "9.*"

Important : Configuration du Context

Si vous avez configuré votre BDDContext pour utiliser SQLite directement dans la méthode OnConfiguring, cela va entrer en conflit avec la base de données en mémoire utilisée pour les tests.

Il faut modifier GameServerApi/Models/BDDContext.cs pour ne configurer SQLite que si aucune autre configuration n’a été fournie :

protected override void OnConfiguring(DbContextOptionsBuilder options)
{
    // Vérifie si une configuration (comme InMemory pour les tests) est déjà présente
    if (!options.IsConfigured)
    {
        // Connexion à la base sqlite par défaut
        options.UseSqlite("Data Source=BDD.db");
    }
}

3. Premier Test : UserService

Nous allons tester la méthode RegisterAsync de UserService.

Créez un fichier UserServiceTests.cs dans le projet de test.

Structure d’un test (AAA)

  • Arrange : Préparer les données et les mocks.
  • Act : Exécuter la méthode à tester.
  • Assert : Vérifier le résultat.

Exemple de test complet

Voici comment tester que l’inscription fonctionne. Copiez ce code et analysez-le.

using GameServerApi.Models;
using GameServerApi.Services;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

namespace GameServerApi.Tests;

public class UserServiceTests
{
    [Fact]
    public async Task RegisterAsync_ShouldCreateUser_WhenValidData()
    {
        // 1. ARRANGE
        
        // Setup InMemory Database
        var options = new DbContextOptionsBuilder<BDDContext>()
            .UseInMemoryDatabase(databaseName: "TestDb_Register") // Nom unique par test
            .Options;
        var context = new BDDContext(options);

        // Setup Mocks
        var passwordHasher = new PasswordHasher<User>();
        
        // Pour JwtService, comme ce n'est pas une interface, le plus simple est d'utiliser le vrai
        // en lui fournissant une configuration mockée.
        var configMock = new Mock<IConfiguration>();
        configMock.Setup(c => c["JWTKey"]).Returns("UneCleSecreteTresLonguePourLesTests123456789");
        var jwtService = new JwtService(configMock.Object);

        var loggerMock = new Mock<ILogger<UserService>>();

        // Création du service à tester
        var userService = new UserService(context, passwordHasher, jwtService, loggerMock.Object);

        // Données de test
        var userPass = new UserPass { Username = "TestUser", Password = "Password123!" };

        // 2. ACT
        var result = await userService.RegisterAsync(userPass);

        // 3. ASSERT
        Assert.NotNull(result.Token);
        Assert.Equal("TestUser", result.User.Username);
        
        // Vérifier que l'user est bien en BDD
        var userInDb = await context.Users.FirstOrDefaultAsync(u => u.Username == "TestUser");
        Assert.NotNull(userInDb);
        Assert.Equal(UserRole.Admin, userInDb.Role); // Le premier user doit être Admin
    }
}

4. Lancer les tests

Exécutez la commande suivante :

dotnet test

Vous devriez voir que le test passe (vert).

5. Tester les cas d’erreur

Un bon testeur vérifie aussi que le code échoue quand il le doit.

Ajoutez un test pour vérifier qu’on ne peut pas créer deux utilisateurs avec le même nom.


Tâches à réaliser

  1. Logging :

    • Injecter ILogger dans UserService, GameService et InventoryService.
    • Ajouter des logs (Info/Warning/Error) aux endroits stratégiques.
    • Vérifier que les logs apparaissent dans la console quand vous lancez l’API.
  2. Tests Unitaires :

    • Configurer le projet GameServerApi.Tests.
    • Écrire les tests pour UserService.RegisterAsync (Succès + Erreur doublon).
    • Écrire les tests pour UserService.LoginAsync (Succès + Erreur mauvais mot de passe).
    • Écrire un test pour InventoryService.PurchaseItemAsync (Vérifier que l’argent est débité et l’item ajouté).

Livrables

Votre projet doit compiler et la commande dotnet test doit réussir avec au moins 5 tests passants.