Contactez-nous

Tests de contexte et de routage

Apprenez à tester efficacement les composants React qui dépendent de l'API Context ou de React Router en utilisant la fonction render et l'option wrapper de React Testing Library.

Le défi : Tester des composants dépendants

De nombreux composants React ne vivent pas en isolation complète. Ils dépendent souvent d'informations ou de fonctionnalités fournies par des éléments plus haut dans l'arbre des composants. Deux des dépendances les plus courantes sont :

  • L'API Context : Les composants utilisent `useContext` pour accéder à des données ou des fonctions partagées (thème, état d'authentification, etc.) fournies par un `Context.Provider`.
  • Le Routage (ex: React Router) : Les composants utilisent des hooks comme `useParams`, `useLocation`, `useNavigate` ou des composants comme `Link`, qui nécessitent tous un contexte de routage fourni par un composant `Router` (comme `BrowserRouter` ou `MemoryRouter`).

Lorsqu'on teste ces composants unitairement ou en intégration avec React Testing Library (RTL), la fonction `render` les monte isolément. Par conséquent, les Providers de contexte ou le Router parent sont absents par défaut. Tenter d'accéder à un contexte ou à une fonctionnalité de routage dans ces conditions provoquera une erreur et fera échouer le test. Nous devons donc trouver un moyen de fournir cet environnement requis pendant le test.

La solution : L'option `wrapper` de `render`

React Testing Library anticipe ce besoin grâce à l'option `wrapper` de sa fonction `render`. Cette option permet de spécifier un composant React qui enveloppera le composant testé au moment du rendu. C'est l'endroit idéal pour insérer les Providers de contexte ou le Router nécessaires.

import { render } from '@testing-library/react';

render(
  , 
  { 
    wrapper: MonWrapperQuiFournitLeContexte // Le composant wrapper
  }
);

Le `MonWrapperQuiFournitLeContexte` serait un composant simple (souvent défini directement dans le fichier de test) qui contient le ou les `Provider(s)` requis.

Tester les composants utilisant l'API Context

Pour tester un composant qui consomme un contexte (par exemple, un contexte de thème), vous devez l'envelopper avec le `Provider` correspondant via l'option `wrapper`.

Exemple :

// ThemeContext.js (simplifié)
import React, { createContext, useState, useContext } from 'react';
const ThemeContext = createContext();
export const ThemeProvider = ({ children, initialTheme = 'light' }) => {
  const [theme, setTheme] = useState(initialTheme);
  const toggleTheme = () => setTheme(t => t === 'light' ? 'dark' : 'light');
  return {children};
};
export const useTheme = () => useContext(ThemeContext);

// ThemedButton.js (composant à tester)
import React from 'react';
import { useTheme } from './ThemeContext';
function ThemedButton() {
  const { theme, toggleTheme } = useTheme();
  return ;
}
export default ThemedButton;

// ThemedButton.test.js
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ThemeProvider } from './ThemeContext'; // Importer le Provider
import ThemedButton from './ThemedButton';

test('affiche le thème initial et le change au clic', async () => {
  // Définir le wrapper qui fournit le contexte
  const Wrapper = ({ children }) => (
    {children}
  );

  render(, { wrapper: Wrapper });

  // Vérifier l'état initial (basé sur initialTheme du wrapper)
  const button = screen.getByRole('button', { name: /thème: dark/i });
  expect(button).toBeInTheDocument();
  expect(button).toHaveClass('dark');

  // Simuler le clic pour changer le thème
  await userEvent.click(button);

  // Vérifier le nouvel état (thème light)
  const updatedButton = screen.getByRole('button', { name: /thème: light/i });
  expect(updatedButton).toBeInTheDocument();
  expect(updatedButton).toHaveClass('light');
});

En fournissant `ThemeProvider` via le `wrapper`, le composant `ThemedButton` peut maintenant utiliser `useTheme()` sans erreur, et nous pouvons tester son comportement en interaction avec le contexte.

Tester les composants utilisant React Router

De manière similaire, les composants utilisant des fonctionnalités de React Router ont besoin d'un contexte de Router. Pour les tests, on utilise quasi systématiquement `MemoryRouter` de `react-router-dom`. `MemoryRouter` stocke l'historique de navigation en mémoire (sans affecter l'URL du navigateur) et permet de définir facilement l'URL initiale pour le test.

Exemple 1 : Tester la navigation avec `Link`

// App.js (simplifié)
import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';
const Home = () => 

Accueil

; const About = () =>

A Propos

; function App() { return (
} /> } />
); } export default App; // App.test.js import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { MemoryRouter } from 'react-router-dom'; // Importer MemoryRouter import App from './App'; test('la navigation fonctionne entre les pages', async () => { // Envelopper App dans MemoryRouter // Pas besoin de 'wrapper' ici, on rend directement le JSX enveloppé render( ); // Vérifier qu'on est sur la page d'accueil initialement expect(screen.getByRole('heading', { name: /accueil/i })).toBeInTheDocument(); // Trouver le lien vers 'A Propos' const aboutLink = screen.getByRole('link', { name: /à propos/i }); // Cliquer sur le lien await userEvent.click(aboutLink); // Vérifier qu'on est maintenant sur la page 'A Propos' expect(screen.getByRole('heading', { name: /à propos/i })).toBeInTheDocument(); });

Exemple 2 : Tester un composant avec `useParams`

// UserProfile.js (simplifié)
import React from 'react';
import { useParams } from 'react-router-dom';

function UserProfile() {
  const { userId } = useParams();
  return 

Profil Utilisateur ID: {userId}

; } export default UserProfile; // UserProfile.test.js import React from 'react'; import { render, screen } from '@testing-library/react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; import UserProfile from './UserProfile'; test('affiche le bon userId depuis les paramètres d\'URL', () => { const fakeUserId = 'test-user-123'; // Définir l'entrée initiale pour MemoryRouter const initialRoute = `/users/${fakeUserId}`; render( // Configurer MemoryRouter avec l'entrée initiale {/* Définir la route correspondant au composant testé */} } /> ); // Vérifier que le composant affiche le userId de l'URL expect(screen.getByRole('heading', { name: `Profil Utilisateur ID: ${fakeUserId}` })).toBeInTheDocument(); });

Pour tester `useNavigate`, vous suivriez une approche similaire : envelopper dans `MemoryRouter`, déclencher l'action qui appelle `navigate()`, puis vérifier que l'interface utilisateur correspond à la nouvelle route attendue. Pour `useLocation`, vous pouvez définir `initialEntries` avec des chemins ou des états spécifiques et vérifier que votre composant les utilise correctement.

Créer des fonctions d'aide (`renderWithProviders`)

Si vous devez fournir les mêmes wrappers (Providers, Router) dans de nombreux tests, il devient vite répétitif de les redéfinir à chaque fois. Une bonne pratique consiste à créer une fonction d'aide personnalisée dans un fichier utilitaire de test (par exemple, `src/test-utils.js`).

// src/test-utils.js
import React from 'react';
import { render } from '@testing-library/react';
import { ThemeProvider } from './ThemeContext';
import { MemoryRouter } from 'react-router-dom';

// Un wrapper combinant plusieurs providers
const AllTheProviders = ({ children }) => {
  return (
     {/* Exemple ajout Router */} 
      
        {children}
      
    
  );
};

// Fonction render personnalisée
const customRender = (ui, options) =>
  render(ui, { wrapper: AllTheProviders, ...options });

// Ré-exporter tout depuis RTL
export * from '@testing-library/react';

// Exporter la fonction render personnalisée
export { customRender as render }; // Renommer en 'render' pour usage facile

// --- Utilisation dans un test ---
// import { render, screen } from './test-utils'; // Importer depuis le fichier d'aide

// test('mon test', () => {
//   render(); // Utilise automatiquement les wrappers
//   // ... assertions ...
// });

Cette approche centralise la configuration de l'environnement de test et rend vos fichiers de test beaucoup plus concis.

Conclusion : Fournir l'environnement nécessaire

Tester des composants qui dépendent du contexte ou du routage dans React est simple une fois que l'on comprend la nécessité de fournir l'environnement manquant. L'option `wrapper` de la fonction `render` de RTL est l'outil clé pour injecter les `Context.Provider` ou le `MemoryRouter` nécessaires.

En utilisant cette technique, combinée à des fonctions d'aide pour éviter la répétition, vous pouvez écrire des tests unitaires et d'intégration efficaces même pour les composants les plus intégrés dans l'écosystème global de votre application React.