Antipatterns dans les tests
Vue d'ensemble
Les tests doivent vérifier le comportement réel, pas le comportement des mocks. Les mocks sont un moyen d'isoler, pas la chose testée.
Principe fondamental : Teste ce que le code fait, pas ce que les mocks font.
Suivre strictement TDD prévient ces antipatterns.
Les lois de fer
1. NE JAMAIS tester le comportement des mocks
2. NE JAMAIS ajouter de méthodes réservées aux tests dans les classes de production
3. NE JAMAIS mocker sans comprendre les dépendances
Antipattern 1 : Tester le comportement des mocks
La violation :
// ❌ MAUVAIS : Vérifier que le mock existe
test('renders sidebar', () => {
render(<Page />);
expect(screen.getByTestId('sidebar-mock')).toBeInTheDocument();
});
Pourquoi c'est mal :
- Tu vérifies que le mock fonctionne, pas que le composant fonctionne
- Le test passe quand le mock est présent, échoue quand il ne l'est pas
- Te dit rien sur le comportement réel
Correction de ton partenaire humain : « Testons-nous le comportement d'un mock ? »
La correction :
// ✅ BON : Teste le composant réel ou ne le mocke pas
test('renders sidebar', () => {
render(<Page />); // Ne mocke pas le sidebar
expect(screen.getByRole('navigation')).toBeInTheDocument();
});
// OU si le sidebar doit être mocké pour l'isolation :
// N'affirme rien sur le mock - teste le comportement de Page avec le sidebar présent
Fonction de garde
AVANT d'affirmer sur un élément de mock :
Demande-toi : « Suis-je en train de tester le comportement du composant réel ou juste l'existence du mock ? »
SI tu testes l'existence du mock :
ARRÊTE - Supprime l'assertion ou dépocke le composant
Teste le comportement réel à la place
Antipattern 2 : Méthodes réservées aux tests en production
La violation :
// ❌ MAUVAIS : destroy() utilisée seulement dans les tests
class Session {
async destroy() {
// Ressemble à une API de production !
await this._workspaceManager?.destroyWorkspace(this.id);
// ... cleanup
}
}
// Dans les tests
afterEach(() => session.destroy());
Pourquoi c'est mal :
- La classe de production est polluée par du code réservé aux tests
- Dangereux si appelé accidentellement en production
- Viole YAGNI et la séparation des préoccupations
- Confond le cycle de vie de l'objet avec celui de l'entité
La correction :
// ✅ BON : Les utilitaires de test gèrent le nettoyage
// Session n'a pas de destroy() - elle est sans état en production
// Dans test-utils/
export async function cleanupSession(session: Session) {
const workspace = session.getWorkspaceInfo();
if (workspace) {
await workspaceManager.destroyWorkspace(workspace.id);
}
}
// Dans les tests
afterEach(() => cleanupSession(session));
Fonction de garde
AVANT d'ajouter une méthode à la classe de production :
Demande-toi : « Cette méthode est-elle utilisée seulement par les tests ? »
SI oui :
ARRÊTE - Ne l'ajoute pas
Mets-la dans les utilitaires de test à la place
Demande-toi : « Cette classe possède-t-elle le cycle de vie de cette ressource ? »
SI non :
ARRÊTE - Mauvaise classe pour cette méthode
Antipattern 3 : Mocker sans comprendre
La violation :
// ❌ MAUVAIS : Mock casse la logique du test
test('detects duplicate server', () => {
// Le mock empêche l'écriture de config que le test dépend !
vi.mock('ToolCatalog', () => ({
discoverAndCacheTools: vi.fn().mockResolvedValue(undefined),
}));
await addServer(config);
await addServer(config); // Devrait lancer - mais ne le fera pas !
});
Pourquoi c'est mal :
- La méthode mockée avait un effet de bord dont le test dépendait (écriture de config)
- Surmocker « par prudence » casse le comportement réel
- Le test passe pour la mauvaise raison ou échoue mystérieusement
La correction :
// ✅ BON : Mocke au bon niveau
test('detects duplicate server', () => {
// Mocke la partie lente, préserve le comportement dont le test a besoin
vi.mock('MCPServerManager'); // Juste mocke le démarrage lent du serveur
await addServer(config); // Config écrite
await addServer(config); // Doublon détecté ✓
});
Fonction de garde
AVANT de mocker une méthode :
ARRÊTE - Ne mocke pas encore
1. Demande-toi : « Quels sont les effets de bord de la méthode réelle ? »
2. Demande-toi : « Ce test dépend-il de ces effets de bord ? »
3. Demande-toi : « Comprends-je vraiment ce dont ce test a besoin ? »
SI le test dépend d'effets de bord :
Mocke à un niveau plus bas (l'opération réelle lente/externe)
OU utilise des doublures de test qui préservent le comportement nécessaire
PAS la méthode de haut niveau dont le test dépend
SI tu ne sais pas de quoi le test a besoin :
D'abord, exécute le test avec l'implémentation réelle
Observe ce qui doit réellement se passer
PUIS ajoute le mocking minimal au bon niveau
Signaux d'alerte :
- « Je vais mocker ça par prudence »
- « Ça pourrait être lent, mieux vaut le mocker »
- Mocker sans comprendre la chaîne de dépendances
Antipattern 4 : Mocks incomplets
La violation :
// ❌ MAUVAIS : Mock partiel - seulement les champs que tu crois utiles
const mockResponse = {
status: 'success',
data: { userId: '123', name: 'Alice' },
// Manquant : metadata que le code en aval utilise
};
// Plus tard : casse quand le code accède à response.metadata.requestId
Pourquoi c'est mal :
- Les mocks partiels cachent les hypothèses structurelles - Tu n'as mocké que les champs que tu connaissais
- Le code en aval peut dépendre de champs que tu n'as pas inclus - Défaillances silencieuses
- Les tests passent mais l'intégration échoue - Mock incomplet, API réelle complète
- Fausse confiance - Le test ne prouve rien sur le comportement réel
La règle de fer : Mocke la structure de données COMPLÈTE telle qu'elle existe en réalité, pas seulement les champs que ton test immédiat utilise.
La correction :
// ✅ BON : Reflète la complétude de l'API réelle
const mockResponse = {
status: 'success',
data: { userId: '123', name: 'Alice' },
metadata: { requestId: 'req-789', timestamp: 1234567890 },
// Tous les champs que l'API réelle retourne
};
Fonction de garde
AVANT de créer des réponses mockées :
Vérife : « Quels champs la réponse réelle de l'API contient-elle ? »
Actions :
1. Examine la réponse réelle de l'API depuis la documentation/exemples
2. Inclus TOUS les champs que le système pourrait consommer en aval
3. Vérifie que le mock correspond complètement au schéma de réponse réelle
Critique :
Si tu crées un mock, tu dois comprendre la STRUCTURE ENTIÈRE
Les mocks partiels échouent silencieusement quand le code dépend de champs omis
Si tu as un doute : Inclus tous les champs documentés
Antipattern 5 : Tests d'intégration en dernier recours
La violation :
✅ Implémentation complète
❌ Aucun test écrit
« Prêt pour les tests »
Pourquoi c'est mal :
- Les tests font partie de l'implémentation, pas d'un suivi optionnel
- TDD aurait attrapé ça
- Tu ne peux pas affirmer que c'est complet sans tests
La correction :
Cycle TDD :
1. Écris un test qui échoue
2. Implémente pour qu'il passe
3. Refactorise
4. PUIS affirme que c'est complet
Quand les mocks deviennent trop complexes
Signaux d'alerte :
- La configuration des mocks est plus longue que la logique du test
- Tu mockes tout pour faire passer le test
- Les mocks manquent des méthodes que les composants réels ont
- Le test casse quand le mock change
Question de ton partenaire humain : « Avons-nous réellement besoin d'utiliser un mock ici ? »
Considère : Les tests d'intégration avec des composants réels sont souvent plus simples que les mocks complexes
TDD prévient ces antipatterns
Pourquoi TDD aide :
- Écris le test d'abord → Te force à penser à ce que tu testes réellement
- Regarde-le échouer → Confirme que le test teste le comportement réel, pas les mocks
- Implémentation minimale → Aucune méthode réservée aux tests ne s'infiltre
- Dépendances réelles → Tu vois ce que le test a réellement besoin avant de mocker
Si tu testes le comportement des mocks, tu as violé TDD - tu as ajouté des mocks sans regarder le test échouer contre le code réel d'abord.
Référence rapide
| Antipattern | Correction |
|---|---|
| Affirmer sur les éléments mock | Teste le composant réel ou dépocke-le |
| Méthodes réservées aux tests | Déplace dans les utilitaires de test |
| Mocker sans comprendre | Comprends d'abord, mocke minimalement |
| Mocks incomplets | Reflète l'API réelle complètement |
| Tests en dernier recours | TDD - tests d'abord |
| Mocks trop complexes | Considère les tests d'intégration |
Signaux d'alerte
- L'assertion vérifie des test IDs en
*-mock - Des méthodes appelées seulement dans les fichiers de test
- La configuration des mocks est >50% du test
- Le test échoue quand tu supprimes le mock
- Tu ne peux pas expliquer pourquoi le mock est nécessaire
- Mocker « juste par prudence »
Le fond du problème
Les mocks sont des outils pour isoler, pas des choses à tester.
Si TDD révèle que tu testes le comportement des mocks, tu as dévié.
Correction : Teste le comportement réel ou remet en question pourquoi tu mockes.