
Tests de composants simples et complexes
Apprenez à appliquer les techniques de test Jest et React Testing Library (RTL) pour valider le comportement des composants React, des plus simples aux plus complexes impliquant état, effets et interactions.
Adapter la stratégie de test à la complexité du composant
Maintenant que nous maîtrisons les outils fondamentaux – rendu avec `render`, sélection d'éléments avec `screen` et ses requêtes, simulation d'interactions avec `userEvent`, et vérification des résultats avec `expect` et `jest-dom` – nous pouvons les appliquer pour tester différents types de composants React. Tous les composants ne sont pas égaux en termes de complexité, et notre approche de test doit s'adapter en conséquence.
Nous distinguerons ici les composants "simples", souvent purement présentationnels, des composants "complexes" qui intègrent une logique interne, gèrent un état, exécutent des effets de bord ou interagissent avec des contextes. L'objectif n'est pas de créer une classification rigide, mais d'illustrer comment les techniques de test se combinent pour couvrir différents scénarios.
Tester les composants simples (Présentationnels)
Un composant simple est généralement un composant qui :
- Reçoit des données principalement via ses props.
- Ne gère pas (ou très peu) d'état interne.
- N'a pas d'effets de bord complexes (`useEffect`).
- Son rendu est une fonction relativement directe de ses props.
Le test d'un tel composant est souvent direct :
- Rendre le composant avec `render`, en lui passant les props nécessaires pour le scénario testé.
- Utiliser les requêtes `screen` (`getByText`, `getByRole`, `getByAltText`, etc.) pour trouver les éléments clés qui devraient être affichés en fonction des props fournies.
- Utiliser les assertions `expect` (souvent avec les matchers `jest-dom`) pour vérifier que ces éléments sont présents (`.toBeInTheDocument()`), qu'ils ont le bon contenu textuel (`.toHaveTextContent()`), les bons attributs (`.toHaveAttribute()`), ou les bonnes classes (`.toHaveClass()`).
Exemple : Composant `UserProfileCard`
// Composant UserProfileCard.js
import React from 'react';
function UserProfileCard({ user }) {
if (!user) {
return Aucun utilisateur sélectionné.;
}
return (
{user.name}
Email: {user.email}
);
}
export default UserProfileCard;
// Test UserProfileCard.test.js
import React from 'react';
import { render, screen } from '@testing-library/react';
import UserProfileCard from './UserProfileCard';
test('affiche les informations utilisateur correctement', () => {
const mockUser = {
name: 'Alice Wonder',
email: 'alice@example.com',
avatarUrl: 'http://example.com/avatar.jpg',
};
render( );
// Vérifier l'image (par alt text)
const avatar = screen.getByAltText(`Avatar de ${mockUser.name}`);
expect(avatar).toBeInTheDocument();
expect(avatar).toHaveAttribute('src', mockUser.avatarUrl);
// Vérifier le nom (par rôle heading et contenu)
expect(screen.getByRole('heading', { name: mockUser.name })).toBeInTheDocument();
// Vérifier l'email (par contenu textuel)
expect(screen.getByText(`Email: ${mockUser.email}`)).toBeInTheDocument();
// Vérifier l'aria-label global (par rôle region implicite et name accessible)
expect(screen.getByRole('generic', { name: `Profil de ${mockUser.name}`})).toBeInTheDocument(); // 'div' a un rôle 'generic' par défaut
});
test('affiche un message si aucun utilisateur n\'est fourni', () => {
render( );
expect(screen.getByText('Aucun utilisateur sélectionné.')).toBeInTheDocument();
});Tester les composants complexes
Les composants complexes introduisent des éléments supplémentaires à tester :
- Etat interne (`useState`, `useReducer`) et interactions : Le test doit simuler des interactions utilisateur (`userEvent.click`, `userEvent.type`, etc.) qui modifient cet état, puis vérifier que le DOM se met à jour correctement en conséquence. On n'inspecte pas l'état lui-même, mais ses effets visibles.
- Effets de bord (`useEffect`) : Si l'effet modifie le DOM (par exemple, après un appel API), utilisez les requêtes `findBy*` pour attendre l'apparition des éléments. Si l'effet dépend de services externes (API, timers), il faut mocker ces dépendances avec Jest (`jest.fn()`, `jest.mock()`) pour isoler le composant et contrôler les réponses.
- Callbacks passés en props : Passez une fonction mock (`jest.fn()`) comme prop, simulez l'événement qui doit déclencher le callback, puis vérifiez que le mock a été appelé (`.toHaveBeenCalled()`, `.toHaveBeenCalledWith(...)`).
- Utilisation de Context : Rendez le composant avec l'option `wrapper` de `render` pour fournir les Providers de contexte nécessaires.
- Rendu conditionnel basé sur l'état : Testez les différents états pour vous assurer que les bons éléments sont affichés ou masqués (`getBy*` vs `queryBy*`).
Exemple : Composant `Counter` (Etat et Interaction)
// Composant Counter.js
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
Compteur: {count}
);
}
export default Counter;
// Test Counter.test.js
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Counter from './Counter';
test('le compteur fonctionne correctement', async () => {
render( );
const incrementButton = screen.getByRole('button', { name: /incrémenter/i });
const decrementButton = screen.getByRole('button', { name: /décrémenter/i });
const countDisplay = screen.getByText(/compteur: 0/i);
// Etat initial
expect(countDisplay).toBeInTheDocument();
expect(decrementButton).toBeDisabled();
// Cliquer sur Incrémenter
await userEvent.click(incrementButton);
expect(screen.getByText(/compteur: 1/i)).toBeInTheDocument();
expect(decrementButton).toBeEnabled(); // Maintenant activé
// Cliquer sur Incrémenter encore
await userEvent.click(incrementButton);
expect(screen.getByText(/compteur: 2/i)).toBeInTheDocument();
// Cliquer sur Décrémenter
await userEvent.click(decrementButton);
expect(screen.getByText(/compteur: 1/i)).toBeInTheDocument();
// Cliquer sur Décrémenter pour revenir à 0
await userEvent.click(decrementButton);
expect(screen.getByText(/compteur: 0/i)).toBeInTheDocument();
expect(decrementButton).toBeDisabled(); // De nouveau désactivé
});Approche pour les Effets (Ex: Appel API)
Pour un composant qui fetch des données :
- Mocker le module de fetch (ex: `axios` ou `fetch` global) avec `jest.mock()`.
- Dans le test, spécifier le comportement du mock pour cet appel (ex: `mockedAxios.get.mockResolvedValueOnce({ data: ... })`).
- Rendre le composant.
- Vérifier l'état de chargement initial si applicable (`screen.getByText(/loading/i)`).
- Utiliser `await screen.findByText(...)` (ou autre requête `findBy*`) pour attendre que les données mockées soient affichées.
- Vérifier que l'indicateur de chargement a disparu (`expect(screen.queryByText(/loading/i)).not.toBeInTheDocument()`).
- Vérifier que la fonction mockée a bien été appelée (`expect(mockedAxios.get).toHaveBeenCalledWith(...)`).
Conclusion : Combiner les techniques
Tester des composants React, qu'ils soient simples ou complexes, repose sur la combinaison judicieuse des outils et techniques fournis par Jest et React Testing Library. L'essentiel est de toujours garder à l'esprit la philosophie de RTL : simuler le comportement utilisateur et vérifier le résultat observable dans le DOM.
Pour les composants simples, l'accent est mis sur la vérification du rendu basé sur les props. Pour les composants complexes, il faut ajouter la simulation d'interactions, la gestion de l'asynchronisme via `findBy*` et `await`, et le mocking des dépendances externes pour isoler le composant et rendre les tests déterministes et rapides.