Tests - rocambille/start-express-react GitHub Wiki

Cette section décrit l'organisation, les objectifs et les bonnes pratiques relatives aux tests dans StartER. L'approche adoptée vise à fournir un environnement reproductible, rapide et isolé, permettant de tester à la fois l'API Express et les composants React.

L'ensemble est basé sur Vitest, supertest, et React Testing Library.

Objectifs

Les tests remplissent plusieurs rôles :

  • Vérifier que l'installation du projet fonctionne (fichiers nécessaires, configuration, connexion à la base).
  • Tester le comportement de l'API en isolation, sans base de données réelle.
  • Tester les composants React à travers des rendus contrôlés.
  • Garantir que les règles métier (autorisation, validation, cohérence) sont respectées.
  • Offrir un environnement stable, reproductible et compatible avec l'exécution en CI/CD.

L'idée n'est pas d'atteindre une couverture maximale, mais de couvrir les points critiques : authentification, accès aux ressources, validation des entrées, cohérence de l'état.

Organisation des tests

Les tests sont regroupés dans le dossier tests/ selon trois catégories :

  • tests/setup/ : tests d'installation et d'environnement.
  • tests/api/ : tests d'intégration de l'API Express.
  • tests/react/ : tests des composants React.

Chaque groupe utilise ses propres outils et stratégies de mock, tout en conservant une philosophie commune : exécuter la logique métier complète en contrôlant l'environnement extérieur (base de données, appels réseau, authentification).

Tests d'installation

Les tests du dossier tests/setup/ valident le bon fonctionnement de l'environnement minimal :

  • existence et contenu du fichier .env
  • initialisation correcte du client MySQL
  • présence des tables définies dans schema.sql

Ces tests sont utiles pour détecter des erreurs fréquentes lors du premier lancement du projet ou lors d'un déploiement automatisé.

Par exemple :

describe("Database connection", () => {
  it("should connect successfully", async () => {
    const connection = await databaseClient.getConnection();

    expect(connection).toBeDefined();

    await connection.release();
  });
});

Tests API

Les tests API sont organisés par module (auth, items...) et utilisent un environnement Express complet, mais avec une base de données entièrement mockée.

Mock de la base de données

L'objectif est de tester l'API sans dépendre d'une instance MySQL. Le fichier tests/api/utils.ts propose une fonction mockDatabaseClient() qui remplace databaseClient.query par une implémentation en mémoire capable de répondre aux requêtes SQL courantes (SELECT, INSERT, UPDATE, DELETE).

Le jeu de données initial (mockedData) est réinitialisé à chaque test par resetMockData() pour garantir l'isolation et la reproductibilité.

Voir le code complet pour les détails :

Mock de l'authentification

Les appels JWT sont mockés au niveau du test afin de contrôler exactement le contexte d'exécution :

vi.spyOn(jwt, "verify").mockImplementation(
  () => ({ sub: "1" })
);

Cela permet notamment de tester les règles d'autorisation. Par exemple : un utilisateur ne peut modifier que ses propres ressources.

Appels HTTP avec supertest

Les tests API utilisent un serveur Express réel monté dans un environnement isolé avec supertest :

const app = express();

app.use(routes);

const api = supertest(app);

const response = await api.get("/api");

Les actions, validateurs et middleware sont donc testés de manière intégrée, tout en restant indépendants d'une base de données externe.

Tests React

Les tests React se trouvent dans tests/react/ et utilisent les outils suivants :

Mock du contexte d'authentification

Le contexte d'authentification peut être mocké pour les composants qui en dépendent :

const auth: ReturnType<typeof AuthContext.useAuth> = {
  user: /* a user or null */,
  check: () => user != null,
  login: vi.fn(),
  logout: vi.fn(),
  register: vi.fn(),
};

vi.spyOn(AuthContext, "useAuth").mockImplementation(() => auth);

L'utilisation de vi.fn() sur les méthodes permet de vérifier leurs appels :

expect(auth.login).toHaveBeenCalledWith(...);

Test de montage des composants

Les tests vérifient principalement que les composants se montent correctement et qu'ils rendent les éléments attendus.

Par exemple :

const Stub = stubRoute("/", Home);

render(<Stub initialEntries={["/"]} />);

await waitFor(() =>
  screen.getByRole("heading", { level: 1, name: /starter/i })
);

L'objectif n'est pas de tester la logique interne de React Router, mais de s'assurer que les composants fonctionnent dans un environnement réaliste.

Pour aller plus loin sur :

Bonnes pratiques

Quelques principes guident l'écriture des tests :

  • Chaque test est isolé Les données mockées et les espions doivent être réinitialisés dans beforeEach et afterEach.

  • Les tests doivent refléter la logique métier réelle Les règles d'autorisation, de validation et de cohérence doivent être explicitement testées.

  • Les tests n'ajoutent pas de complexité inutile Les mocks doivent rester simples et contrôlés, sans dépendances externes.

  • Les tests doivent rester stables et prédictibles Pas de dépendances à des services externes ou à une base réelle.

  • Le style doit rester proche d'une documentation vivante Chaque test fournit un exemple lisible et cohérent du comportement attendu de l'API ou des composants.

CLI

Pour lancer l'ensemble des tests et collecter la couverture

docker compose run --build --rm server npm run test

Pour lancer un dossier de test précis, par exemple tests/react :

docker compose run --build --rm server npm run test react

Pour lancer un fichier de test précis, par exemple tests/react/item.test.tsx :

docker compose run --build --rm server npm run test react/item