Pourquoi Tester React avec Jest ?
Tester React avec Jest est essentiel pour assurer la qualité et la robustesse des composants React que vous développez. En effet, les applications modernes sont souvent complexes et contiennent de nombreuses interactions entre différents composants. Les tests permettent de vérifier que chaque composant fonctionne correctement sous différentes conditions et d'interagir avec d'autres composants.
Un cas concret est la création d'une application e-commerce où l'on a besoin de tester un bouton "Ajouter au panier". Ce bouton doit non seulement ajouter le produit à un état local, mais aussi envoyer une requête à l'API pour mettre à jour les données du serveur. Un test Jest permettrait de vérifier que le bouton fonctionne correctement en ajoutant le produit et en vérifiant qu'une requête est bien envoyée.
Prerequis
Avant de commencer, il est nécessaire d'avoir les connaissances suivantes :
- Connaissance avancée de React
- Familiarité avec les hooks (useState, useEffect)
- Compréhension des composants fonctionnels et des classes
- Compétences en JavaScript ES6+
- Pratique dans la création et la gestion des tests unitaires
Les outils nécessaires à installer sont :
- Node.js (v14.0 ou plus tard)
- npm (v6.0 ou plus tard)
Concepts fondamentaux
1. Jest : Un Framework de Test JavaScript
Jest est un framework de test JavaScript créé par Facebook pour les tests unitaires, d'intégration et d'end-to-end. Il a été conçu pour être rapide, simple à utiliser et facilement extensible.
// schema mental : Jest est une bibliothèque qui permet de tester des fonctions en vérifiant leurs résultats
2. Expect : Assertions
Expect est un utilitaire fourni par Jest pour faire des assertions sur les valeurs retournées par vos fonctions ou composants.
// schema mental : Expect est utilisé pour décrire ce que vous attendez de votre code
react
// Exemple de test avec expect
test('2 + 2 est égal à 4', () => {
expect(2 + 2).toBe(4);
});
3. Render : Mocking React Components
Jest permet également de rendre et de simuler les composants React en utilisant la fonction render du module @testing-library/react. Cela vous permet d'isoler le comportement des composants sans dépendre des autres composants.
// schema mental : Render est utilisé pour exécuter un composant React et simuler son environnement
react
import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';
test('renders learn react link', () => {
render(<MyComponent />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
4. Mock API Calls with Fetch
Pour tester des composants qui font appel à une API, vous pouvez utiliser les mocks. Jest permet de simuler les appels API en utilisant la fonction mockImplementation du module jest.
// schema mental : Mock API calls est utilisé pour tester des composants qui dépendent d'une API sans faire des appels réels
react
import { render, screen } from '@testing-library/react';
import fetchMock from 'fetch-mock-jest';
import MyComponent from './MyComponent';
beforeAll(() => {
fetchMock.mock('https://api.example.com/data', { data: 'fakeData' });
});
afterAll(() => {
fetchMock.restore();
});
test('renders fetched data', async () => {
render(<MyComponent />);
const dataElement = await screen.findByText(/fakeData/i);
expect(dataElement).toBeInTheDocument();
});
Mise en pratique : projet fil rouge
Commençons par créer un mini-projet complet et réaliste : un gestionnaire de tâches. Nous allons créer une application simple qui permet d'ajouter, de supprimer et de marquer comme terminée des tâches.
Étape 1 : Créer le projet
Créons un nouveau projet React avec Create React App :
npx create-react-app task-manager
cd task-manager
Étape 2 : Créer les composants
Créons deux composants : TaskList et AddTask.
TaskList.js
// src/TaskList.js
import React, { useState } from 'react';
const TaskList = () => {
const [tasks, setTasks] = useState([]);
const handleDelete = (id) => {
setTasks(tasks.filter(task => task.id !== id));
};
const handleToggle = (id) => {
setTasks(tasks.map(task =>
task.id === id ? { ...task, completed: !task.completed } : task
));
};
return (
<div>
<ul>
{tasks.map(task => (
<li key={task.id} style=textDecoration: task.completed ? 'line-through' : 'none'>
{task.text}
<button onClick={() => handleToggle(task.id)}>Toggle</button>
<button onClick={() => handleDelete(task.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
};
export default TaskList;
AddTask.js
// src/AddTask.js
import React, { useState } from 'react';
const AddTask = ({ onAdd }) => {
const [text, setText] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (!text.trim()) return;
onAdd({ id: Date.now(), text, completed: false });
setText('');
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Ajouter une tâche"
/>
<button type="submit">Add</button>
</form>
);
};
export default AddTask;
Étape 3 : Créer la structure des fichiers
## src/
├── App.js
├── TaskList.js
└── AddTask.js
Étape 4 : Ajouter les composants au fichier principal
// src/App.js
import React from 'react';
import TaskList from './TaskList';
import AddTask from './AddTask';
function App() {
const [tasks, setTasks] = useState([]);
const addTask = (task) => {
setTasks([...tasks, task]);
};
return (
<div className="App">
<h1>Task Manager</h1>
<AddTask onAdd={addTask} />
<TaskList tasks={tasks} />
</div>
);
}
export default App;
Étape 5 : Ajouter des tests
Créons des tests pour les composants TaskList et AddTask.
TaskList.test.js
// src/TaskList.test.js
import React from 'react';
import { render, screen } from '@testing-library/react';
import TaskList from './TaskList';
test('renders task list', () => {
const tasks = [
{ id: 1, text: 'Faire les courses', completed: false },
{ id: 2, text: 'Nettoyer la maison', completed: true }
];
render(<TaskList tasks={tasks} />);
expect(screen.getByText(/faire les courses/i)).toBeInTheDocument();
expect(screen.getByText(/nettoyer la maison/i)).toBeInTheDocument();
});
AddTask.test.js
// src/AddTask.test.js
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import AddTask from './AddTask';
test('renders add task form', () => {
render(<AddTask />);
expect(screen.getByPlaceholderText(/ajouter une tâche/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /add/i })).toBeInTheDocument();
});
test('adds a new task when submitted', async () => {
const onAddMock = jest.fn();
render(<AddTask onAdd={onAddMock} />);
fireEvent.change(screen.getByPlaceholderText(/ajouter une tâche/i), { target: { value: 'Faire les courses' } });
fireEvent.click(screen.getByRole('button', { name: /add/i }));
await screen.findByText(/faire les courses/i);
expect(onAddMock).toHaveBeenCalledWith({ id: expect.any(Number), text: 'Faire les courses', completed: false });
});
Erreurs frequentes et debugging
1. Mauvaise assertion
Code incorrect :
expect(2 + 2).toBe(5);
Code correct :
expect(2 + 2).toBe(4);
2. Mocking des appels API avec fetch manquant
Code incorrect :
test('renders fetched data', async () => {
render(<MyComponent />);
const dataElement = await screen.findByText(/fakeData/i);
expect(dataElement).toBeInTheDocument();
});
Code correct :
import fetchMock from 'fetch-mock-jest';
beforeAll(() => {
fetchMock.mock('https://api.example.com/data', { data: 'fakeData' });
});
afterAll(() => {
fetchMock.restore();
});
test('renders fetched data', async () => {
render(<MyComponent />);
const dataElement = await screen.findByText(/fakeData/i);
expect(dataElement).toBeInTheDocument();
});
3. Composant non rendu
Code incorrect :
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
Code correct :
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
Pour aller plus loin
- Mocking des composants avec
jest.mock: Permet de simuler les comportements des modules importés. - Testing Asynchronous Code : Apprendre à tester du code asynchrone avec Jest, tel que les appels API ou l'animation CSS.
- Test Driven Development (TDD) : Lire un tutoriel sur TDD pour voir comment tester avant de coder.
Défi pratique
Créez une application simple qui permet d'afficher des livres en utilisant une API publique, comme le JSONPlaceholder. Créez des composants pour afficher la liste des livres et permettre l'ajout de nouveaux livres via un formulaire.
Ressources :