En bref
- La répartition des tests dépend de ce que le projet peut casser. Un site sans backend se protège surtout par des tests de logique et de contenu, une application avec base de données par des tests d’intégration.
- Sur ce portfolio, les tests Vitest vérifient la logique pure et le contenu lui-même (liens entre articles, longueur des descriptions, typographie), sans Testing Library ni Playwright.
- Sur mon socle Payload, les tests d’intégration tournent sur une vraie base PostgreSQL, et quelques parcours Playwright s’exécutent sur le build de production, dont un audit axe de chaque route publique.
- Le taux de couverture ne sert pas d’objectif ; un test vaut par le comportement qu’il protège.
Sur un projet React ou Next.js, la vraie question porte sur ce qu’il faut tester, avec quel outil et jusqu’où aller. Je n’y réponds pas de la même façon sur ce portfolio, qui n’a pas de backend, sur Les Chemins de la Maison, un site vitrine avec un blog, et sur mon socle Next.js × Payload, qui embarque une base de données, un back-office et des comptes d’administration avec des rôles. Les exemples qui suivent viennent de leurs dépôts.
Pourquoi la répartition dépend-elle du projet ?
Un test automatisé sert d’abord à détecter une régression avant la production, puis à refactoriser avec confiance. Le taux de couverture mesure le code exécuté et ne dit rien de la pertinence des assertions. Je ne lui fixe donc pas d’objectif chiffré, et un test qui ne protège aucun comportement réel n’est pas ajouté pour la métrique.
Les niveaux de tests n’ont pas le même coût. Un test unitaire vérifie une fonction en quelques millisecondes, un test de composant rend une interface dans un DOM simulé, un test d’intégration fait travailler plusieurs briques ensemble, souvent avec une base de données, et un test de bout en bout pilote un vrai navigateur sur l’application complète. Je mets l’essentiel de l’effort sur les niveaux rapides et ne monte que lorsque le risque se trouve plus haut, ce qui donne des répartitions très différentes d’un projet à l’autre :
- ce portfolio, plus de 500 tests Vitest, tous sur de la logique pure ou du contenu ;
- Les Chemins de la Maison, près de 190 tests Vitest, sur le listing du blog, les métadonnées, les tarifs et l’intégrité des articles ;
- le socle Payload, plus d’une centaine de fichiers de tests unitaires et de composants, 55 fichiers de tests d’intégration et 7 parcours Playwright.
En équipe, le dosage suit aussi les habitudes du projet. Sur la refonte d’un back-office chez Meilleurtaux Assurances, les tests unitaires avec React Testing Library faisaient partie du quotidien.
Que tester sur un site sans backend ?
Sur ce portfolio, le contenu (projets, articles, compétences) est typé dans des fichiers TypeScript et les pages en dérivent. Ce qui peut casser, c’est la logique qui transforme ces données, comme les builders JSON-LD, le flux RSS ou la validation du formulaire de contact, et le contenu lui-même. Une partie des tests vérifie donc des règles éditoriales, par exemple les espaces insécables avant la ponctuation, l’unicité des textes alternatifs ou les liens entre articles :
// tests/unit/data/blogData.content.spec.ts (simplified) it.each(blogPosts.map((p) => [p.slug, p] as const))( '%s is linked from at least two other pages', (_slug, post) => { const fromProjects = projects.filter((project) => getPostsForProject(project).includes(post)).length; const fromArticles = blogPosts.filter((other) => other !== post && getRelatedPosts(other).includes(post)).length; expect(fromProjects + fromArticles).toBeGreaterThanOrEqual(2); }, ); Ce test échoue dès qu’un nouvel article n’est relié à aucune autre page. Il tient en quelques lignes et protège chaque ajout de contenu, là où une relecture pourrait passer à côté. Les Chemins de la Maison suit la même logique, notamment sur la pagination et les filtres du blog, rendus côté serveur à partir de l’URL, et sur les tarifs, qui alimentent à la fois les pages et les données structurées.
Aucun des deux sites n’a de tests de composants ni de parcours Playwright. Leurs composants sont surtout visuels, et le typage strict, le build de production et la vérification à l’écran suffisent pour l’instant. Les règles de test du portfolio excluent d’ailleurs les tests de rendu purement visuels, comme les détails d’implémentation (état interne, structure exacte du DOM) qu’un refactoring changerait sans rien casser.
Sur ce portfolio comme sur le socle, la logique nouvelle ou corrigée s’écrit en TDD, avec le test d’abord, vu en échec, puis le code minimal qui le fait passer. Un bug corrigé laisse ainsi derrière lui un test qui échouait avant la correction. L’article sur Claude Code montre comment ce cycle s’intègre à mon workflow.
Quand passer aux tests de composants et d’intégration ?
Le socle Payload change d’échelle. Ses blocs de page builder et ses formulaires sont de vrais composants à comportement, testés avec React Testing Library dans un DOM simulé (jsdom). Les tests les retrouvent par leur rôle ou leur libellé, comme le ferait un utilisateur, ce qui vérifie au passage qu’un champ a bien un nom accessible.
Une grande partie du risque se trouve pourtant côté données, avec les droits d’accès de chaque collection, la redirection créée quand un slug change, la purge des données expirées ou le rate limiting. Ces règles passent par Payload et PostgreSQL, et les simuler reviendrait à tester le mock plutôt que le comportement. Les tests d’intégration tournent donc sur une vraie base, via la Local API de Payload :
// tests/int/redirectOnSlugChange.int.spec.ts (simplified) it('follows successive renames without a redirect chain', async () => { const page = await createPage(`v1-${RUN}`); await payload.update({collection: 'pages', id: page.id, data: {slug: `v2-${RUN}`}}); await payload.update({collection: 'pages', id: page.id, data: {slug: `v3-${RUN}`}}); const first = await redirectFrom(`/v1-${RUN}`); expect(first && resolveRedirectDestination(first)).toBe(`/v3-${RUN}`); }); Les Server Components asynchrones posent une limite à ce niveau. La documentation de Next.js indique que certains outils ne les prennent pas encore entièrement en charge et recommande pour eux les tests de bout en bout. Dans le socle, une bonne partie de la logique qu’ils appellent est extraite dans des fonctions testées en intégration, et leur rendu est couvert par Playwright.
Quels parcours confier à Playwright ?
Les tests de bout en bout coûtent le plus cher à écrire et à maintenir, et le socle n’en compte que sept. Ils couvrent les parcours dont une régression bloquerait un visiteur ou un administrateur, comme la page d’accueil, la connexion avec un code reçu par e-mail, le mot de passe oublié, l’administration, le blog et les pages créées dans le CMS. Le septième vérifie l’accessibilité de chaque route publique avec axe et échoue sur toute violation sérieuse ou critique des WCAG A et AA. Il s’assure d’abord que la page existe, sans quoi axe auditerait la page 404 et passerait sans rien contrôler :
// tests/e2e/accessibility.e2e.spec.ts (simplified) async function auditPage(page: Page, path: string) { const response = await page.goto(path); expect(response?.status(), `${path} must exist`).toBe(200); const results = await new AxeBuilder({page}) .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']) .analyze(); const seriousOrCritical = results.violations.filter( (violation) => violation.impact === 'serious' || violation.impact === 'critical', ); expect(seriousOrCritical).toEqual([]); } En CI, ces parcours tournent sur le build de production plutôt que sur le serveur de développement, car certains bugs n’existent que dans le code assemblé pour la production. C’est sur ce build qu’est apparu un code d’erreur de la connexion par e-mail que pnpm dev ne montrait pas. Un audit axe reste par ailleurs un complément. Il repère une partie des problèmes d’accessibilité et ne remplace pas une vérification manuelle au clavier et au lecteur d’écran.
Dans quel ordre la CI lance-t-elle tout ça ?
La CI du socle va du plus rapide au plus coûteux, pour échouer le plus tôt possible :
- typage et lint, puis audit des dépendances ;
- tests unitaires et d’intégration, sur une base PostgreSQL dédiée ;
- build de production ;
- parcours Playwright sur ce build, après un seed du contenu de base ;
- smoke test des images Docker avant leur publication, qui lance la configuration de production réelle sur ces images pour vérifier ce que les autres étapes ne voient pas.
Une vérification qui échoue bloque la suite, et un test instable se corrige au lieu d’être relancé jusqu’au vert. À force de relances, plus personne ne croit aux échecs, et le filet perd sa valeur.
D’un projet à l’autre, je me pose la même question, à savoir ce qui peut casser et quel test le verrait au moindre coût. Sur ce portfolio, la réponse tient dans Vitest et des tests de contenu ; sur le socle Payload, elle passe par une vraie base et un navigateur. Le portfolio n’a pas encore de test de composant, et le menu mobile ou le formulaire de contact en seraient les premiers candidats.
Pour en discuter, écrivez-moi.
Sources
- Next.js — tests (Server Components asynchrones)
- Testing Library — principes directeurs
- Vitest — guide
- Playwright — tests d’accessibilité
Publié à l’origine sur jordan-philippot.fr.



