Qu’est-ce que Testing Library ?
Testing Library est une famille de bibliothèques qui vous aident à tester des composants UI de la même manière que l’utilisateur les expérimente. Au lieu de s’appuyer sur les mécanismes internes d’un composant, vous le rendez, vous recherchez les éléments comme le ferait une personne — via leur rôle, leur label ou leur texte visible — et vous interagissez avec eux à travers des événements réalistes.
L’idée centrale est une réaction contre les tests basés sur les détails d’implémentation. Les approches plus anciennes rendaient un composant puis effectuaient des assertions sur l’état interne, les props ou une structure DOM spécifique. Ces tests échouaient à chaque refactorisation tout en détectant très peu de bugs réels. Testing Library inverse la perspective : si l’utilisateur peut le voir et l’utiliser, vous pouvez le tester, et le test survit aux modifications apportées au fonctionnement interne du composant.
Rendu et requêtes
Vous rendez un composant, puis vous interrogez le DOM résultant via screen.
// LoginForm.test.jsx
import { render, screen } from "@testing-library/react";
import { LoginForm } from "./LoginForm";
test("shows the email field", () => {
render(<LoginForm />);
expect(screen.getByLabelText("Email")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Sign in" })).toBeInTheDocument();
});
screen est la surface de requête recommandée. Elle recherche dans l’ensemble du document rendu, ce qui reflète la manière dont un utilisateur voit la page plutôt qu’un conteneur spécifique.
Choisir la bonne requête
Testing Library propose de nombreuses requêtes, et l’ordre de priorité est important. Privilégiez celles qui se rapprochent le plus de la perspective de l’utilisateur :
- getByRole — boutons, liens, titres, champs de saisie et plus encore, recherchés par leur nom accessible. C’est le meilleur choix par défaut.
- getByLabelText — contrôles de formulaire trouvés via leur label.
- getByPlaceholderText — lorsque le placeholder est le seul label disponible.
- getByText — contenu textuel non interactif.
- getByDisplayValue — la valeur actuelle d’un élément de formulaire.
- getByAltText — images via leur texte alternatif.
- getByTitle et getByTestId — en dernier recours.
// queries.jsx
screen.getByRole("heading", { level: 1, name: "Dashboard" });
screen.getByLabelText("Password");
screen.getByText("Welcome back");
screen.getByAltText("Company logo");
screen.getByTestId("chart");
getByRole est puissant car il fait également office de vérification d’accessibilité. Si un contrôle n’a pas de nom accessible, la requête échoue — ce qui signifie que votre balisage est probablement inaccessible lui aussi.
Chaque requête possède des variantes : getBy lève une erreur si rien ne correspond, queryBy retourne null, et findBy retourne une promesse qui attend la résolution. Utilisez queryBy pour affirmer que quelque chose est absent, et findBy après une mise à jour asynchrone.
Interagir avec userEvent
userEvent simule la séquence complète d’événements générés par un utilisateur réel.
// Search.test.jsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Search } from "./Search";
test("submits the query", async () => {
const onSearch = vi.fn();
render(<Search onSearch={onSearch} />);
await userEvent.type(screen.getByLabelText("Search"), "vitest");
await userEvent.click(screen.getByRole("button", { name: "Go" }));
expect(onSearch).toHaveBeenCalledWith("vitest");
});
userEvent gère conjointement le focus, le clavier et les événements de pointeur, ce qui permet de détecter des bugs qu’un simple fireEvent.change ne verrait pas. Privilégiez-le pour tout, sauf dans les rares cas où il ne couvre pas vos besoins.
Comportement asynchrone
La plupart des composants se mettent à jour de manière asynchrone, que ce soit suite à un fetch, un timer ou une transition. Utilisez findBy et waitFor plutôt que des délais arbitraires.
// Users.test.jsx
test("renders users after loading", async () => {
render(<Users />);
expect(screen.getByText("Loading…")).toBeInTheDocument();
const items = await screen.findAllByRole("listitem");
expect(items).toHaveLength(3);
});
findBy attend qu’un élément correspondant apparaisse et échoue avec un message explicite s’il n’apparaît jamais. waitFor est utilisé pour attendre une assertion ou une condition qui ne correspond pas à une simple recherche d’élément. N’utilisez jamais de timeouts réels ; ils rendent les tests lents et instables.
Assertions avec jest-dom
Le package compagnon @testing-library/jest-dom ajoute des matchers qui permettent de lire naturellement l’état du DOM.
// matchers.test.jsx
expect(button).toBeDisabled();
expect(input).toHaveValue("[email protected]");
expect(dialog).not.toBeInTheDocument();
expect(heading).toHaveTextContent("Dashboard");
expect(link).toHaveAttribute("href", "/docs");
Ces matchers rendent l’intention plus claire que les vérifications brutes des propriétés du DOM et produisent de meilleurs messages d’erreur en cas d’échec.
Tests axés sur l’accessibilité
Comme getByRole s’appuie sur les rôles et les noms accessibles, rédiger vos tests de cette manière permet de détecter les problèmes d’accessibilité très tôt. Si vous ne parvenez pas à trouver un bouton par son rôle, les utilisateurs de lecteurs d’écran ne pourront pas le trouver non plus. L’ajout de getByRole et toHaveAccessibleName à vos tests est l’un des moyens les plus simples et les moins coûteux d’améliorer l’accessibilité, et cela remplace souvent la nécessité d’un audit automatisé distinct.
Testing Library à travers les frameworks
La bibliothèque propose des adaptateurs pour React, Vue, Svelte, Angular et d’autres encore. L’API de requête est commune, le modèle mental reste donc le même même si la configuration diffère. L’adaptateur React est le plus utilisé et c’est celui que nous présentons ici.
Bonnes pratiques
- Effectuez vos requêtes d’abord par rôle, puis par label, puis par texte ; n’utilisez les test ids qu’en dernier recours.
- Utilisez
userEventau lieu defireEvent. - Attendez
findByetwaitForplutôt que d’utiliser des délais. - Effectuez vos assertions sur ce que l’utilisateur voit, et non sur l’état interne.
- Gardez vos tests focalisés sur un seul comportement chacun.
- Nettoyez automatiquement entre les tests ; Testing Library le fait par défaut.
- Considérez l’échec d’un
getByRolecomme un indice signalant un problème d’accessibilité.
Erreurs courantes
- Utiliser
container.querySelectorpour tester les noms de classes. - Utiliser
fireEventalors queuserEventpermettrait de détecter davantage d’erreurs. - Ajouter des attentes
setTimeoutarbitraires au lieu d’utiliserfindBy. - Faire des snapshots de l’intégralité de l’arbre rendu.
- Effectuer des assertions sur l’état ou les props du composant plutôt que sur le DOM.
- Ajouter des test ids partout et perdre ainsi les bénéfices liés à l’accessibilité.
Et après ?
Testing Library constitue la couche composants d’une stratégie de test solide. Utilisez-la avec Vitest ou Jest, approfondissez vos connaissances en React pour rendre vos composants testables, et couvrez des parcours utilisateurs complets avec Playwright. Pour commencer, prenez un composant que vous avez déjà créé et écrivez un test du point de vue de l’utilisateur.