Qu’est-ce que Jest ?
Jest est un test runner JavaScript créé par Facebook, devenu aujourd’hui l’un des outils de test les plus utilisés de l’écosystème. Il est batteries-included : les assertions, le mocking, le snapshot testing et la couverture de code sont tous inclus dans un seul package, et un nouveau projet nécessite très peu de configuration pour démarrer.
Pendant des années, Jest a été le choix par défaut pour les projets React et Node.js, et il reste dominant dans les bases de code existantes. Même avec la montée en puissance de Vitest, l’API de Jest constitue le vocabulaire commun des tests JavaScript, ce qui rend son apprentissage indispensable, quel que soit le runner que vous utilisez au quotidien.
Écrire des tests
Un fichier de test Jest utilise describe pour grouper et test (ou it) pour déclarer un cas de test.
// cart.test.js
import { Cart } from "./cart";
describe("Cart", () => {
test("starts empty", () => {
expect(new Cart().items).toEqual([]);
});
test("adds an item", () => {
const cart = new Cart();
cart.add({ id: 1, price: 10 });
expect(cart.items).toHaveLength(1);
});
});
Utilisez beforeEach et afterEach pour la configuration et le nettoyage (setup/teardown), et beforeAll et afterAll lorsqu’une ressource doit être créée une seule fois pour l’ensemble du fichier. Nommez vos tests en fonction du comportement attendu afin qu’un échec vous indique précisément ce qui ne fonctionne plus.
Matchers
L’API expect de Jest est vaste et expressive.
// matchers.test.js
expect(value).toBe(5); // strict equality
expect(value).toEqual({ a: 1 }); // deep equality
expect(value).toBeDefined();
expect(list).toContain("a");
expect(fn).toThrow("invalid");
expect(value).toBeGreaterThan(3);
expect(obj).toMatchObject({ id: 1 }); // partial match
toBe compare avec Object.is et convient parfaitement aux types primitifs. toEqual compare récursivement les objets et les tableaux. toStrictEqual est plus strict concernant les types et les propriétés undefined. toMatchObject est utile lorsque vous ne vous intéressez qu’à un sous-ensemble d’une structure.
Mocks, spies et mocking de modules
Le système de mocking de Jest est l’une de ses fonctionnalités les plus puissantes.
// users.test.js
import { jest } from "@jest/globals";
import { getUser } from "./users";
jest.mock("./http", () => ({
get: jest.fn().mockResolvedValue({ id: 1, name: "Ada" }),
}));
test("returns a user", async () => {
await expect(getUser(1)).resolves.toEqual({ id: 1, name: "Ada" });
});
jest.fn crée une fonction mock, jest.spyOn enveloppe une méthode réelle pour vous permettre d’observer les appels et de la restaurer plus tard, et jest.mock remplace un module. Comme jest.mock est “hoisted” (hissé) au-dessus des imports, le mock est en place avant le chargement du module testé.
Réinitialisez toujours vos mocks entre chaque test. Activez clearMocks, resetMocks ou restoreMocks dans la configuration, ou appelez l’assistant ...AllMocks correspondant dans afterEach. Un état de mock qui persiste d’un test à l’autre est l’une des raisons les plus courantes pour lesquelles des tests réussissent individuellement mais échouent lorsqu’ils sont lancés ensemble.
Tests asynchrones
Utilisez async/await avec resolves et rejects, exactement comme vous le feriez dans Vitest.
// async.test.js
test("rejects on failure", async () => {
await expect(loadUser(-1)).rejects.toThrow("Invalid id");
});
Le callback done fonctionne, mais il est facile de faire des erreurs. Privilégiez l’utilisation de await. Pour les timers, jest.useFakeTimers() combiné à jest.advanceTimersByTime() permet de rendre les tests de debounce et de retry déterministes.
Snapshots
Les snapshots sérialisent une valeur et la comparent lors des exécutions futures.
// config.test.js
test("builds the default config", () => {
expect(createConfig({ debug: true })).toMatchInlineSnapshot();
});
Les snapshots inline résident dans le fichier de test, ce qui permet de les examiner via un diff. Les snapshots externes sont pratiques pour les sorties volumineuses, mais elles sont fréquemment mises à jour sans inspection, ce qui en fait une simple formalité. Utilisez les snapshots pour des données sérialisables stables, et non comme un substitut à une réflexion sur les points essentiels.
Configuration
Jest lit sa configuration depuis jest.config.js, une clé jest dans package.json ou un flag CLI.
// jest.config.js
export default {
testEnvironment: "jsdom",
clearMocks: true,
collectCoverage: true,
coverageThreshold: {
global: { lines: 80, functions: 80 },
},
transform: {
"^.+\\.(t|j)sx?$": ["@swc/jest"],
},
};
testEnvironment permet de choisir entre node ou jsdom. clearMocks empêche les fuites de mémoire. Les seuils de couverture (coverage thresholds) transforment un objectif en une règle contraignante. Le transform détermine comment TypeScript et JSX sont compilés, avec ts-jest, Babel et SWC comme options courantes.
Où se place Jest
Jest constitue la couche de tests unitaires et d’intégration. Pour le comportement des composants, associez-le à Testing Library afin de tester du point de vue de l’utilisateur. Pour les flux utilisateurs complets, utilisez un outil de bout en bout tel que Playwright ou Cypress. Si vous lancez un nouveau projet Vite, Vitest propose la même API avec un pipeline partagé plus rapide.
Bonnes pratiques
- Testez le comportement via les API publiques, et non via les mécanismes internes privés.
- Maintenez une seule assertion logique par test, lorsque cela est possible.
- Réinitialisez et restaurez les mocks entre chaque test.
- Privilégiez
async/awaitàdone. - Utilisez des fake timers pour tout ce qui dépend du temps.
- Effectuez des snapshots de structures petites et stables, et examinez chaque mise à jour.
- Exécutez les tests en CI à chaque modification, et pas seulement localement.
Erreurs courantes
- Faire des assertions sur des détails d’implémentation, ce qui casse les tests lors des refactorisations.
- Mettre à jour les snapshots sans vérification.
- Oublier de réinitialiser les mocks, entraînant des fuites d’état entre les tests.
- Utiliser des timers réels, rendant les tests lents ou instables (flaky).
- Tout tester au niveau unitaire et passer à côté de bugs d’intégration.
- Ignorer la couverture de code tout en supposant que les tests couvrent les chemins critiques.
Et après ?
Jest constitue une base solide et un vocabulaire commun à travers tout l’écosystème. Comparez-le avec Vitest pour vos projets Vite modernes, ajoutez Testing Library pour vos composants, et couvrez vos parcours utilisateurs avec Playwright ou Cypress. Ensuite, écrivez un test pour un bug que vous avez récemment corrigé afin de vous protéger contre les régressions.