Mocks, stubs et tests d'intégration
Doubles de dépendances, service Nest testé, vraie base.
Objectifs
À la fin de cette leçon, vous saurez :
- remplacer une dépendance par un mock/stub ;
- tester un service NestJS avec sa dépendance substituée ;
- écrire un test d'intégration contre une vraie base.
🔗 Pour vous rafraîchir la mémoire : modules : import/export · méthodes HTTP et codes de statut
Isoler avec un double
Le service dépend du repository (DIP, chapitre 31) — le test exploite cette frontière :
const fakeRepo = {
findAll: jest.fn().mockResolvedValue([{ id: 1, titre: "A", fait: false }]),
};
it("lister renvoie les tâches", async () => {
const service = new TasksService(fakeRepo as never);
const taches = await service.lister();
expect(taches).toHaveLength(1);
});
Vocabulaire précis :
| Double | Rôle |
|---|---|
| stub | renvoie des données fixes (pas d'attente sur son usage) |
| mock | attend des appels précis qu'on VÉRIFIE |
expect(fakeRepo.findAll).toHaveBeenCalledTimes(1); // assertion sur le MOCK
Règle de sobriété : stubber les frontières techniques (base, HTTP, horloge) ; ne jamais mocker ce que l'on teste. Un test qui mocke tout teste... ses propres mocks.
Tester un service NestJS
// admin.service.spec.ts — style du projet Devmind : repository mocké
const repository = {
listCourses: jest.fn(),
} as unknown as AdminRepository;
it("lève NotFound si le cours est absent", async () => {
repository.findCourse = jest.fn().mockResolvedValue(null);
const service = new AdminService(repository);
await expect(service.getCourse("x")).rejects.toBeInstanceOf(NotFoundException);
});
Pas besoin de module Nest complet pour tester la logique : new Service(fake) suffit. Les tests e2e Nest (@nestjs/testing) montent l'application réelle — réservés aux parcours critiques.
Intégration : la vraie base
Les unitaires prouvent la logique ; seuls les tests d' intégration prouvent le SQL :
it("crée puis retrouve une tâche", async () => {
const repo = new TasksRepository(testPool); // vraie base de test
const creee = await repo.creer({ titre: "Intégration" });
const relue = await repo.trouver(creee.id);
expect(relue.titre).toBe("Intégration");
});
Prérequis : une base dédiée aux tests (jamais la base dev !), nettoyée entre les tests (TRUNCATE ou transactions annulées). C'est la priorité n°1 des prochaines étapes du projet Devmind.
Tester un endpoint avec supertest
Entre le test unitaire et l'E2E se place le test d'API : on démarre l'application en mémoire et on lui parle en HTTP réel.
import * as request from "supertest";
import { Test } from "@nestjs/testing";
import { AppModule } from "./app.module";
const app = await Test.createTestingModule({ imports: [AppModule] })
.overrideProvider(TachesRepository) // double à la frontière technique
.useValue({ lister: () => [{ id: 1, titre: "test" }] })
.compile();
await app.init();
it("POST /api/tasks → 201", async () => {
const res = await request(app.getHttpServer())
.post("/api/tasks")
.send({ titre: "acheter du pain" });
expect(res.status).toBe(201);
expect(res.body.titre).toBe("acheter du pain");
});
On teste alors la chaîne complète — routage, DTO/pipes, statuts — sans base réelle (le repository est doublé). C'est le niveau recommandé pour vos endpoints critiques.
L'E2E, même en miniature
Au sommet de la pyramide : piloter un vrai navigateur. Avec Playwright :
import { test, expect } from "@playwright/test";
test("ajouter une tâche", async ({ page }) => {
await page.goto("http://localhost:3000");
await page.getByPlaceholder("Nouvelle tâche").fill("tester l'E2E");
await page.getByRole("button", { name: "Ajouter" }).click();
await expect(page.getByText("tester l'E2E")).toBeVisible();
});
Quelques tests de ce type sur les parcours vitaux (connexion, création, publication) valent mieux que zéro E2E « parfait » — c'est précisément le chantier prioritaire ouvert dans ce dépôt.
Exercice
- Stubbez l'horloge : testez
estExpire(session)sans attendre. - Testez UserService contre un fakeRepo : succès + absence.
- Pourquoi mocker l'horloge plutôt que sleep(2000) dans le test ?
Résumé
- DIP rend tout testable : injectez des doubles par constructeur.
- Stub = données ; mock = attentes vérifiées.
- L'intégration contre vraie base reste irremplaçable pour le SQL.
Correction disponibleCherchez d’abord par vous-même.Voir la correction
Correction
Réponses détaillées
Question 3. Le sleep ralentit TOUTE la suite à chaque exécution et reste flaky (parfois trop court). L'horloge simulée avance quand VOUS décidez : rapide et déterministe. Contrôle du temps = règle d'or des tests fiables.