Dans le chapitre précédent, votre tech lead vous a demandé de sécuriser les routes API de l'application de gestion de tâches. Vous avez appris à écrire des tests d'intégration back-end pour vérifier qu'un utilisateur pouvait se connecter, obtenir un jeton d'authentification, créer une tâche puis consulter sa liste de tâches. Ces tests vous ont permis de valider le comportement des route handlers Next.js sans démarrer de serveur.
Grâce à ce travail, vous disposez désormais d'une couche back-end fiable. Pourtant, une question demeure : que se passe-t-il du côté de l'interface utilisateur ? Même si les routes fonctionnent parfaitement, un utilisateur peut rencontrer des problèmes si un composant React n'affiche pas correctement les données ou si un bouton ne déclenche pas l'action attendue.
Votre tech lead souhaite donc franchir une nouvelle étape. Cette fois, vous allez tester les composants React eux-mêmes. Vous découvrirez React Testing Library, l'outil de référence pour tester des interfaces React, et vous apprendrez à vérifier ce qu'un utilisateur voit réellement à l'écran lorsqu'il interagit avec l'application.
Depuis le début du cours, vous progressez dans les différents niveaux de test d'une application moderne. Après les tests unitaires puis les tests d'intégration back-end, vous allez maintenant découvrir le troisième niveau : le test de composant front-end.
Un test de composant consiste à vérifier le comportement d'un composant React de manière isolée. L'objectif n'est pas de tester une route API ni de reproduire un parcours complet dans un navigateur. Nous cherchons simplement à vérifier qu'un composant affiche les bonnes informations et réagit correctement aux actions de l'utilisateur.
Dans l'application fil rouge, nous allons nous appuyer sur le composantTaskList, chargé d'afficher les tâches d'un utilisateur.
Lorsqu'un utilisateur consulte sa liste de tâches, plusieurs questions se posent :
Les tâches sont-elles correctement affichées ?
Le composant présente-t-il les bons éléments d'interface ?
Les actions disponibles réagissent-elles comme prévu ?
Les changements d'état sont-ils visibles à l'écran ?
Le test de composant permet précisément de répondre à ces questions.
Ici, le composant est testé seul, dans un environnement contrôlé. Les dépendances externes sont remplacées ou simulées afin de se concentrer uniquement sur ce que l'utilisateur voit et peut faire à l'intérieur du composant.
Cette approche permet d'identifier rapidement les problèmes d'affichage ou d'interaction sans devoir exécuter l'ensemble de l'application. C'est ce qui fait des tests de composants un excellent complément aux tests unitaires et aux tests d'intégration.
Pour tester des composants React, l'écosystème moderne s'appuie principalement sur React Testing Library, souvent abrégée RTL.
Cette bibliothèque est aujourd'hui recommandée par l'équipe React et largement utilisée dans les projets Next.js. Son succès repose sur une idée simple :
Tester ce que voit et fait l'utilisateur, plutôt que les détails d'implémentation du composant.
Cette philosophie est importante. Lorsqu'un utilisateur utilise votre application, il ne sait pas quels hooks sont utilisés ni comment les états internes sont organisés. En revanche, il voit un bouton, un champ de saisie ou une liste de tâches. C'est donc sur ces éléments visibles que les tests doivent se concentrer.
Pour installer les outils nécessaires, votre équipe ajoute les dépendances suivantes :
npm install -D \
@testing-library/react \
@testing-library/user-event \
@testing-library/jest-dom \
jsdomLa configuration de Vitest doit ensuite être complétée afin d'utiliser l'environnementjsdomet de charger les extensions de React Testing Library.
Le fichiervitest.setup.ts(déclaré dans l'optionsetupFilesdevitest.config.ts) contient généralement :
import "@testing-library/jest-dom";Cette configuration permet d'utiliser des assertions supplémentaires telles que :
expect(element).toBeInTheDocument();
expect(element).toBeVisible();React Testing Library fournit également plusieurs outils que vous utiliserez fréquemment :
render()pour afficher le composant dans le DOM virtuel ;
screenpour rechercher des éléments visibles ;
userEventpour simuler les actions de l'utilisateur.
Ces trois outils constitueront la base de tous les tests de composants que vous écrirez dans la suite du parcours.
Votre tech lead vous demande maintenant de tester le composantTaskList. Avant même de simuler une interaction, il est important de vérifier que le composant affiche correctement son contenu lorsqu'il est rendu. Pour cela, nous utilisons la fonctionrender(). Chaque tâche fournie respecte le typePublicTask, soit{ id, title, completed, createdAt }: le champcompletedpilote notamment l'affichage barré et l'ordre des tâches.
render(
<TaskList
tasks={[
{
id: "task-1",
title: "Acheter du pain",
completed: false,
createdAt: new Date().toISOString(),
},
]}
/>
);Une fois le composant affiché dans le DOM virtuel, React Testing Library permet d'interroger l'interface grâce à l'objetscreen.
Par exemple :
screen.getByRole("list");ou encore :
screen.getByText("Acheter du pain");Nous pouvons ensuite vérifier la présence des éléments :
expect(
screen.getByText("Acheter du pain")
).toBeInTheDocument();À ce stade, il est essentiel d'adopter les bonnes habitudes de recherche proposées par RTL.
La bibliothèque privilégie les requêtes qui reflètent l'expérience réelle de l'utilisateur. L'ordre recommandé est le suivant :
getByRole()
getByLabelText()
getByText()
getByTestId()
Pourquoi cet ordre ?
Parce qu'un utilisateur interagit naturellement avec des boutons, des champs ou des listes identifiables par leur rôle ou leur libellé. À l'inverse, un attributdata-testidn'existe que pour les développeurs.
Avant d’écrire ce test d’interaction, observez dans ce screencast comment reproduire un vrai geste utilisateur.
À vous maintenant de reproduire ce parcours côté composant, en vous concentrant sur ce que l’utilisateur voit et fait réellement.
Une interface utilisateur devient réellement intéressante lorsqu'elle réagit aux actions de l'utilisateur. Dans notre application fil rouge, le composantTaskListcomporte un bouton « Nouvelle tâche ». Lorsque l'utilisateur clique dessus, un formulaire apparaît afin de saisir un nouveau titre. Attention à ne pas le confondre avec le bouton « Ajouter la tâche », qui se trouve dans le formulaire et sert à le soumettre. Pour reproduire ce comportement dans un test, nous utilisonsuserEvent.
La première étape consiste à créer un utilisateur simulé :
const user = userEvent.setup();Nous déclenchons ensuite un clic sur le bouton qui ouvre le formulaire :
await user.click(
screen.getByRole("button", {
name: /nouvelle tâche/i
})
);Après ce clic, le champ de saisie apparaît à la suite d'un changement d'état. Comme les actionsuserEventsont asynchrones, nous utilisonsfindByRole(), qui attend que l'élément soit présent avant de le renvoyer :
const input = await screen.findByRole(
"textbox",
{ name: /titre/i }
);Nous simulons ensuite la saisie. Choisissez un titre distinct des libellés de l'interface pour éviter toute ambiguïté lors de la recherche :
await user.type(
input,
"Réviser le chapitre"
);Nous soumettons le formulaire en cliquant sur le bouton « Ajouter la tâche » :
await user.click(
screen.getByRole("button", {
name: /ajouter la tâche/i
})
);Enfin, nous vérifions que la nouvelle tâche apparaît dans la liste :
expect(
await screen.findByText("Réviser le chapitre")
).toBeInTheDocument();Deux règles méritent d'être retenues.
La première est d'utiliser systématiquementuserEventplutôt que de déclencher artificiellement des événements. Cette bibliothèque reproduit des comportements proches de ceux d'un véritable utilisateur.
La seconde concerne le choix des requêtes :
getBy*pour les éléments disponibles immédiatement ;
findBy*pour les éléments qui apparaissent après une opération asynchrone.
Cette distinction évite de nombreux faux positifs dans les tests.
Tous les comportements d'un composant ne se traduisent pas nécessairement par une modification visible de l'interface. Dans certains cas, un composant doit simplement prévenir son parent qu'une action a eu lieu. C'est souvent le rôle d'un callback passé en prop.
Prenons l'exemple du composantTaskForm. Lorsqu'un utilisateur soumet le formulaire, le composant appelle une fonctiononSubmit()fournie par son parent.
Comment vérifier cet appel dans un test ?
Vitest fournit une solution très simple :vi.fn().
Cette fonction crée un mock capable d'enregistrer tous les appels qui lui sont adressés.
Nous commençons par créer le mock :
const onSubmit = vi.fn();Puis nous le passons au composant :
render(
<TaskForm onSubmit={onSubmit} />
);Après avoir simulé la saisie et la soumission du formulaire, nous pouvons vérifier l'appel :
expect(onSubmit)
.toHaveBeenCalledWith({
title: "Nouvelle tâche"
});Cette technique est extrêmement utile lorsque le comportement attendu consiste à transmettre une information plutôt qu'à modifier directement l'affichage. Elle permet également d'isoler le composant testé sans devoir exécuter le reste de l'application.
À mesure que le nombre de tests augmente, leur lisibilité devient aussi importante que leur exactitude. Un fichier mal organisé peut rapidement devenir difficile à maintenir, même si tous les tests passent. Pour éviter cela, votre équipe applique trois règles simples.
La première consiste à regrouper les scénarios par comportement dans des blocsdescribe()explicites.
Par exemple :
describe("TaskList rendering", () => {
// ...
});La deuxième règle est de nommer les tests du point de vue de l'utilisateur.
Préférez :
it(
"should display the task list when tasks are provided",
() => {}
);plutôt que :
it("test 1", () => {});Enfin, lorsque plusieurs tests utilisent le même rendu, il est utile d'extraire une fonction utilitaire :
function renderTaskList(props) {
return render(
<TaskList {...props} />
);
}Cette organisation réduit les duplications et facilite la lecture des scénarios.

Après avoir sécurisé les routes API dans le chapitre précédent, votre tech lead souhaite désormais renforcer la qualité de l'interface utilisateur.
L'équipe a déjà testé le backend de création de tâches. Il faut maintenant vérifier que le composantTaskFormpermet réellement à un utilisateur de saisir une tâche et de transmettre les bonnes informations au reste de l'application.
Vérifiez le rendu initial du composantTaskForm.
Contrôlez la présence du champ de saisie et du bouton de soumission.
Simulez la saisie du titre « Nouvelle tâche ».
Simulez l'envoi du formulaire avecuserEvent.
Vérifiez que le callbackonSubmitest appelé avec les bonnes données grâce àvi.fn().
Organisez vos tests dans des blocsdescribe()explicites.
Un test de composant vérifie ce que voit et fait l'utilisateur, sans tester l'implémentation interne.
React Testing Library utiliserender(),screenetuserEventpour tester les composants React dans un DOM virtuel.
Les requêtes doivent privilégiergetByRole()puisgetByLabelText()avant d'utilisergetByText()ougetByTestId().
vi.fn()permet de vérifier qu'un callback React est appelé avec les bonnes données.
Une organisation claire des tests avec desdescribe()explicites facilite la maintenance du projet.
Vous savez maintenant tester des composants React de manière isolée. Dans le prochain chapitre, vous découvrirez comment reproduire des parcours utilisateur complets dans un navigateur grâce aux tests end-to-end.