Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 430 minIntermédiaire

Tests unitaires en JavaScript avec Vitest (et Jest)

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • installer Vitest et lancer les tests d'un projet Node.js ;
  • choisir le bon matcher et tester une exception ou du code asynchrone ;
  • écrire des tests paramétrés et contrôler la date avec les faux minuteurs ;
  • lire un test qui échoue et passer d'un test Jest à un test Vitest.

4 exercices corrigés

Vitest est le framework de test JavaScript et TypeScript de l'écosystème Vite : il trouve les fichiers *.test.js, exécute les tests qu'ils contiennent et affiche un bilan, en quelques millisecondes et sans configuration pour un projet moderne en modules ES. Son API (describe, test, expect, vi.fn) reprend celle de Jest, l'outil historique que tu croiseras encore dans beaucoup de projets existants. Ce chapitre teste les règles de Prêt'Matos avec Vitest 5 et montre ce qui change avec Jest 30.

Vitest ou Jest ?

Vitest 5Jest 30
Modules ES (import)Natifs, sans configurationExpérimentaux : il faut lancer Node avec --experimental-vm-modules, ou rester en CommonJS (require)
TypeScript, JSXPris en charge directement (transformés par Vite)Demande une configuration (Babel, ts-jest ou un autre transformateur)
Fonctions de testImportées depuis vitest (ou globales avec l'option globals: true)Globales : pas d'import
Doubluresvi.fn(), vi.spyOn(), vi.mock()jest.fn(), jest.spyOn(), jest.mock()
Mode surveillancePar défaut dans un terminal (vitest)Avec l'option --watch
Où tu le croisesProjets Vite, React, Vue récents ; nouveaux projets Node.jsProjets existants, React Native, nombreux tutoriels

Pour un nouveau projet, Vitest est le choix le plus simple en 2026. Si ton projet utilise déjà Jest, garde-le : les concepts sont identiques et les tests se ressemblent ligne pour ligne. Les cours Maintenance applicative et Intégration continue utilisent Jest, ce cours utilise Vitest : tu passeras de l'un à l'autre sans difficulté.

Installer Vitest et lancer les tests

Vitest 5 demande Node.js 22.12 ou plus (Node.js 24 LTS est le bon choix). Dans le projet Prêt'Matos, dont le package.json contient "type": "module" :

bash
npm install --save-dev vitest

--save-dev range Vitest dans les devDependencies : il sert à développer, pas à faire tourner l'application en production. On ajoute ensuite des scripts dans package.json :

json
{
  "type": "module",
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest",
    "test:couverture": "vitest run --coverage"
  }
}
CommandeEffet
npm testLance tous les tests une fois et rend la main : c'est ce que fera la CI
npm run test:watchLance les tests, puis les relance automatiquement à chaque enregistrement d'un fichier
npx vitest run src/regles.test.jsLance un seul fichier
npx vitest run -t "16 jours"Lance seulement les tests dont le nom contient « 16 jours »
npm run test:couvertureLance les tests et mesure la couverture de code (chapitre 12)

Par défaut, Vitest cherche les fichiers dont le nom se termine par .test.js ou .spec.js (et leurs variantes .ts, .jsx, .tsx, .mjs…), partout dans le projet sauf dans node_modules. Deux organisations sont courantes : le fichier de test à côté du fichier testé (src/regles.js et src/regles.test.js), ou un dossier tests/ séparé. Ce cours range les tests à côté du code : on les voit, on les met à jour en même temps.

La suite de tests des règles de Prêt'Matos

Voici le fichier de test complet de src/regles.js. Lis-le en entier : il applique tout ce que dit le chapitre 3.

js
// src/regles.test.js
import { describe, test, expect } from 'vitest';
import { dureeEnJours, periodesSeChevauchent, verifierReservation, joursDeRetard } from './regles.js';

describe('dureeEnJours', () => {
  test('compte le premier et le dernier jour', () => {
    expect(dureeEnJours('2027-03-12', '2027-03-18')).toBe(7);
  });

  test('vaut 1 pour un prêt rendu le jour même', () => {
    expect(dureeEnJours('2027-03-12', '2027-03-12')).toBe(1);
  });

  test('traverse la fin du mois de février', () => {
    expect(dureeEnJours('2027-02-26', '2027-03-02')).toBe(5);
  });
});

describe('periodesSeChevauchent', () => {
  const pc07 = { debut: '2027-03-10', fin: '2027-03-14' };

  test.each([
    { cas: 'commence pendant', debut: '2027-03-12', fin: '2027-03-18', attendu: true },
    { cas: 'finit le premier jour', debut: '2027-03-05', fin: '2027-03-10', attendu: true },
    { cas: 'englobe', debut: '2027-03-01', fin: '2027-03-31', attendu: true },
    { cas: 'commence le lendemain', debut: '2027-03-15', fin: '2027-03-20', attendu: false },
    { cas: 'finit la veille', debut: '2027-03-01', fin: '2027-03-09', attendu: false },
  ])('une période qui $cas → $attendu', ({ debut, fin, attendu }) => {
    expect(periodesSeChevauchent(pc07, { debut, fin })).toBe(attendu);
  });
});

describe('verifierReservation', () => {
  const aujourdhui = '2027-03-08';
  const reservations = [{ materiel: 'PC-07', debut: '2027-03-10', fin: '2027-03-14' }];

  test('accepte un matériel libre sur toute la période', () => {
    const demande = { materiel: 'PC-07', debut: '2027-03-15', fin: '2027-03-18' };

    const resultat = verifierReservation(demande, reservations, aujourdhui);

    expect(resultat).toEqual({ acceptee: true });
  });

  test('refuse un matériel déjà réservé sur une partie de la période', () => {
    const demande = { materiel: 'PC-07', debut: '2027-03-12', fin: '2027-03-18' };

    const resultat = verifierReservation(demande, reservations, aujourdhui);

    expect(resultat.acceptee).toBe(false);
    expect(resultat.motif).toBe('PC-07 est déjà réservé du 10/03/2027 au 14/03/2027.');
  });

  test('ignore les réservations des autres matériels', () => {
    const demande = { materiel: 'VP-02', debut: '2027-03-12', fin: '2027-03-13' };

    expect(verifierReservation(demande, reservations, aujourdhui).acceptee).toBe(true);
  });

  test("accepte une réservation qui commence aujourd'hui", () => {
    const demande = { materiel: 'VP-02', debut: '2027-03-08', fin: '2027-03-09' };

    expect(verifierReservation(demande, reservations, aujourdhui).acceptee).toBe(true);
  });

  test('refuse une date de début passée', () => {
    const demande = { materiel: 'VP-02', debut: '2027-03-07', fin: '2027-03-09' };

    expect(verifierReservation(demande, reservations, aujourdhui).motif).toBe('La date de début est déjà passée.');
  });

  test('accepte un prêt de 15 jours, la durée maximale', () => {
    const demande = { materiel: 'VP-02', debut: '2027-03-15', fin: '2027-03-29' };

    expect(verifierReservation(demande, reservations, aujourdhui).acceptee).toBe(true);
  });

  test('refuse un prêt de 16 jours', () => {
    const demande = { materiel: 'VP-02', debut: '2027-03-15', fin: '2027-03-30' };

    expect(verifierReservation(demande, reservations, aujourdhui).motif).toBe('Un prêt dure au plus 15 jours.');
  });

  test('refuse une date de fin avant la date de début', () => {
    const demande = { materiel: 'VP-02', debut: '2027-03-20', fin: '2027-03-19' };

    expect(verifierReservation(demande, reservations, aujourdhui).motif).toBe('La date de fin précède la date de début.');
  });
});

describe('joursDeRetard', () => {
  test('vaut 0 le jour du retour prévu', () => {
    expect(joursDeRetard('2027-03-14', '2027-03-14')).toBe(0);
  });

  test('vaut 0 avant le retour prévu', () => {
    expect(joursDeRetard('2027-03-14', '2027-03-10')).toBe(0);
  });

  test('compte les jours après le retour prévu', () => {
    expect(joursDeRetard('2027-03-14', '2027-03-17')).toBe(3);
  });
});

Et voici le code testé, en entier :

js
// src/regles.js : les règles de réservation de Prêt'Matos
export const DUREE_MAX_JOURS = 15;

const MS_PAR_JOUR = 24 * 60 * 60 * 1000;

// « 2027-03-12 » devient un numéro de jour : on compare des jours, pas des heures.
function numeroDeJour(dateIso) {
  const [annee, mois, jour] = dateIso.split('-').map(Number);
  return Date.UTC(annee, mois - 1, jour) / MS_PAR_JOUR;
}

// « 2027-03-12 » devient « 12/03/2027 ».
export function formaterDate(dateIso) {
  const [annee, mois, jour] = dateIso.split('-');
  return `${jour}/${mois}/${annee}`;
}

// Du 12 au 18 mars : 7 jours, le premier et le dernier compris.
export function dureeEnJours(debut, fin) {
  return numeroDeJour(fin) - numeroDeJour(debut) + 1;
}

// Deux périodes se chevauchent si chacune commence avant la fin de l'autre.
export function periodesSeChevauchent(a, b) {
  return numeroDeJour(a.debut) <= numeroDeJour(b.fin) && numeroDeJour(b.debut) <= numeroDeJour(a.fin);
}

export function verifierReservation(demande, reservations, aujourdhui) {
  if (numeroDeJour(demande.fin) < numeroDeJour(demande.debut)) {
    return { acceptee: false, motif: 'La date de fin précède la date de début.' };
  }
  if (numeroDeJour(demande.debut) < numeroDeJour(aujourdhui)) {
    return { acceptee: false, motif: 'La date de début est déjà passée.' };
  }
  if (dureeEnJours(demande.debut, demande.fin) > DUREE_MAX_JOURS) {
    return { acceptee: false, motif: `Un prêt dure au plus ${DUREE_MAX_JOURS} jours.` };
  }
  const conflit = reservations.find(
    (r) => r.materiel === demande.materiel && periodesSeChevauchent(r, demande),
  );
  if (conflit) {
    return {
      acceptee: false,
      motif: `${demande.materiel} est déjà réservé du ${formaterDate(conflit.debut)} au ${formaterDate(conflit.fin)}.`,
    };
  }
  return { acceptee: true };
}

// Nombre de jours de retard d'un prêt à la date du jour (0 s'il n'est pas en retard).
export function joursDeRetard(retourPrevu, aujourdhui) {
  return Math.max(0, numeroDeJour(aujourdhui) - numeroDeJour(retourPrevu));
}

On lance la suite :

text
$ npx vitest run src/regles.test.js

 ✓ src/regles.test.js (19 tests) 6ms

 Test Files  1 passed (1)
      Tests  19 passed (19)

Avec --reporter=verbose, Vitest affiche chaque test sur sa ligne : le nom du describe, puis celui du test. C'est là qu'on voit l'intérêt des noms en forme de phrase.

text
 ✓ src/regles.test.js > periodesSeChevauchent > une période qui commence pendant → true 1ms
 ✓ src/regles.test.js > periodesSeChevauchent > une période qui finit le premier jour → true 0ms
 ✓ src/regles.test.js > verifierReservation > refuse un prêt de 16 jours 0ms

Les blocs describe et les fonctions de préparation

describe regroupe les tests d'une même fonction ou d'un même scénario. Les variables déclarées dans un describe (comme aujourdhui et reservations) sont partagées par ses tests : à condition qu'aucun test ne les modifie. Dès qu'un test modifie une donnée commune, il faut la recréer avant chaque test avec beforeEach :

js
import { describe, test, expect, beforeEach } from 'vitest';

describe('le dépôt en mémoire', () => {
  let depot;

  beforeEach(() => {
    // Un dépôt neuf avant CHAQUE test : aucun test ne voit les ajouts d'un autre.
    depot = creerDepotMemoire(jeuDeTest());
  });

  test('ajoute une réservation', async () => {
    await depot.ajouterReservation({ materiel: 'VP-02', debut: '2027-03-15', fin: '2027-03-18', salarie: 3 });

    expect(await depot.reservationsDu('VP-02')).toHaveLength(1);
  });

  test('ne contient au départ aucune réservation de VP-02', async () => {
    expect(await depot.reservationsDu('VP-02')).toHaveLength(0);
  });
});
FonctionExécutée…Usage typique
beforeEachavant chaque test du blocRecréer les données, remettre une doublure à zéro
afterEachaprès chaque test du blocRemettre la vraie horloge, nettoyer le DOM
beforeAllune fois, avant le premier test du blocOuvrir une connexion à une base de test
afterAllune fois, après le dernier test du blocFermer la connexion

Les matchers essentiels

Un matcher est la méthode appelée sur expect(…) pour comparer. Tous peuvent être inversés avec .not : expect(liste).not.toContain('PC-07').

MatcherVérifie que…Exemple
toBe(v)la valeur est identique (Object.is) : nombres, chaînes, booléensexpect(duree).toBe(7)
toEqual(obj)le contenu est égal, récursivement : objets et tableauxexpect(resultat).toEqual({ acceptee: true })
toStrictEqual(obj)comme toEqual, en tenant compte aussi des propriétés undefined et des classesexpect(reservation).toStrictEqual(attendue)
toMatchObject(obj)l'objet contient au moins ces propriétésexpect(reponse.body).toMatchObject({ materiel: 'VP-02' })
toContain(x)un tableau contient l'élément, ou une chaîne la sous-chaîneexpect(codes).toContain('VP-02')
toHaveLength(n)la longueur d'un tableau ou d'une chaîneexpect(reservations).toHaveLength(1)
toBeNull(), toBeUndefined(), toBeDefined()la valeur est null, undefined ou définieexpect(depot.trouver(99)).toBeNull()
toBeTruthy(), toBeFalsy()la valeur est vraie ou fausse au sens de JavaScriptà éviter quand toBe(true) est possible
toBeGreaterThan(n), toBeLessThanOrEqual(n)comparaison numériqueexpect(id).toBeGreaterThan(1)
toBeCloseTo(n)un nombre à virgule est proche de nexpect(0.1 + 0.2).toBeCloseTo(0.3)
toMatch(regex)une chaîne respecte une expression régulièreexpect(motif).toMatch(/déjà réservé/)
toThrow(…)une fonction lève une exceptionvoir plus bas

La confusion la plus fréquente est toBe sur un objet :

text
AssertionError: expected { acceptee: true } to be { acceptee: true } // Object.is equality

If it should pass with deep equality, replace "toBe" with "toStrictEqual"

Deux objets créés séparément ne sont jamais le même objet, même avec le même contenu : toBe compare les références. Pour comparer le contenu, utilise toEqual. Vitest le suggère d'ailleurs dans son message.

Tester une exception

Pour vérifier qu'une fonction lève une erreur, on passe à expect une fonction qui fait l'appel, pas l'appel lui-même :

js
// src/dates.js
export function exigerDateIso(valeur) {
  if (!/^\d{4}-\d{2}-\d{2}$/.test(valeur)) {
    throw new TypeError(`Date invalide : ${valeur}`);
  }
  return valeur;
}
js
test('lève une TypeError pour une date au format français', () => {
  expect(() => exigerDateIso('14/03/2027')).toThrow(TypeError);
  expect(() => exigerDateIso('14/03/2027')).toThrow('Date invalide : 14/03/2027');
});

Si tu écris expect(exigerDateIso('14/03/2027')).toThrow(), l'exception est levée avant qu'expect ne soit appelé : le test échoue avec l'erreur de la fonction, au lieu de la constater. Le () => retarde l'appel pour que Vitest puisse l'attraper.

Tester du code asynchrone

Les fonctions qui accèdent à une base ou au réseau renvoient des promesses. Un test asynchrone est une fonction async, et chaque appel asynchrone est attendu avec await :

js
test('ajoute une réservation et la retrouve', async () => {
  const depot = creerDepotMemoire(jeuDeTest());

  await depot.ajouterReservation({ materiel: 'VP-02', debut: '2027-03-15', fin: '2027-03-18', salarie: 3 });
  const reservations = await depot.reservationsDu('VP-02');

  expect(reservations).toHaveLength(1);
});

Pour vérifier directement le résultat d'une promesse, resolves et rejects existent, toujours précédés de await :

js
await expect(depot.reservationsDu('VP-02')).resolves.toHaveLength(0);
await expect(lireFichierAbsent()).rejects.toThrow('ENOENT');

Les tests paramétrés : test.each

Quand plusieurs tests ne diffèrent que par leurs données, on les écrit une fois avec test.each. Chaque ligne du tableau donne un test séparé, avec son propre nom et son propre résultat. Avec des lignes en objets, $cas ou $attendu dans le nom sont remplacés par la valeur de la propriété correspondante : c'est ce que fait le bloc periodesSeChevauchent ci-dessus. On peut aussi écrire les lignes en tableaux, et utiliser %s (chaîne), %i (entier) ou %o (objet) dans le nom :

js
test.each([
  ['2027-03-31', '2027-04-01'],
  ['2027-12-31', '2028-01-01'],
  ['2028-02-28', '2028-02-29'],
])('le lendemain du %s est le %s', (date, attendu) => {
  expect(lendemain(date)).toBe(attendu);
});

Contrôler le temps avec les faux minuteurs

Certaines fonctions doivent lire l'horloge : la date du jour envoyée à verifierReservation vient bien de quelque part. Cette fonction donne la date du jour à Paris, au format AAAA-MM-JJ :

js
// src/dates.js
// La date du jour à Paris, au format AAAA-MM-JJ (le format suédois « sv-SE » est justement celui-ci).
export function aujourdhuiAParis() {
  return new Date().toLocaleDateString('sv-SE', { timeZone: 'Europe/Paris' });
}

Pour la tester, Vitest remplace l'horloge par une horloge simulée : vi.useFakeTimers() l'active, vi.setSystemTime() choisit l'instant, vi.useRealTimers() rend la vraie.

js
import { describe, test, expect, vi, afterEach } from 'vitest';
import { aujourdhuiAParis } from './dates.js';

describe('aujourdhuiAParis', () => {
  afterEach(() => {
    vi.useRealTimers();
  });

  test('donne la date de Paris, pas celle de Londres', () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date('2027-03-13T23:30:00Z')); // 00 h 30 le 14 mars à Paris

    expect(aujourdhuiAParis()).toBe('2027-03-14');
  });
});

Ce test choisit exprès un instant piège : 23 h 30 en temps universel, soit 0 h 30 le lendemain à Paris (heure d'hiver, UTC+1). Une implémentation naïve avec new Date().toISOString().slice(0, 10) renverrait 2027-03-13, la date de Londres. Le test passe quel que soit le fuseau de la machine qui le lance, ce qui le rend reproductible.

Les faux minuteurs servent aussi pour setTimeout et setInterval : vi.advanceTimersByTime(60_000) fait avancer l'horloge d'une minute instantanément, sans attendre.

Isoler, sauter, marquer à faire

ÉcritureEffet
test.only('…', …)Seuls les tests marqués only du fichier s'exécutent : pratique pour déboguer, à ne jamais committer
test.skip('…', …)Le test est sauté et compté comme tel dans le bilan
test.todo('refuse un matériel en réparation')Un test à écrire : il apparaît dans le bilan comme un rappel
describe.skipIf(condition)('…', …)Saute tout le bloc si la condition est vraie, par exemple quand aucune base de test n'est configurée (chapitre 8)

Configurer Vitest

Sans configuration, Vitest teste du code Node.js. Un fichier vitest.config.js à la racine (ou une clé test dans vite.config.js pour un projet Vite) change les réglages :

js
// vitest.config.js
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    environment: 'node',          // 'jsdom' pour tester des composants React (chapitre 9)
    include: ['src/**/*.test.js'], // où chercher les tests
    coverage: {
      provider: 'v8',
      include: ['src/**/*.js'],
    },
  },
});

Lire un test qui échoue

Un test rouge est une information, pas une catastrophe. Voici ce qu'affiche Vitest si l'on remplace, dans periodesSeChevauchent, le premier <= par < :

text
 ❯ src/regles.test.js (19 tests | 1 failed) 7ms
   ❯ periodesSeChevauchent (5)
     × une période qui finit le premier jour → true 4ms

 FAIL  src/regles.test.js > periodesSeChevauchent > une période qui finit le premier jour → true
AssertionError: expected false to be true // Object.is equality

- Expected
+ Received

- true
+ false

 ❯ src/regles.test.js:29:57

Lis-le dans cet ordre :

  1. Le nom du test : « une période qui finit le premier jour ». Tu sais déjà quel cas est cassé : une limite.
  2. L'assertion : on attendait true, on a obtenu false. Le code ne voit plus de chevauchement quand la demande finit le jour où la réservation commence.
  3. L'emplacement : la ligne 29 du fichier de test, avec l'extrait du code.
  4. Les tests verts : les 18 autres passent, dont « commence pendant » et « englobe ». Le défaut ne touche que la frontière, ce qui oriente directement vers un < ou un <=.

Et avec Jest ?

Le même test, écrit pour Jest 30 dans un projet en CommonJS :

js
// regles.test.js, version Jest : describe, test et expect sont globaux
const { dureeEnJours } = require('./regles.cjs');

describe('dureeEnJours', () => {
  test('compte le premier et le dernier jour', () => {
    expect(dureeEnJours('2027-03-12', '2027-03-18')).toBe(7);
  });
});
bash
npm install --save-dev jest
npx jest
VitestJest
import { describe, test, expect, vi } from 'vitest'rien à importer : describe, test, expect, jest sont globaux
vi.fn(), vi.spyOn(), vi.mock()jest.fn(), jest.spyOn(), jest.mock()
vi.useFakeTimers(), vi.setSystemTime()jest.useFakeTimers(), jest.setSystemTime()
vitest run, vitestjest, jest --watch
vitest.config.jsjest.config.js

Les matchers (toBe, toEqual, toThrow…) sont les mêmes. Pour migrer un projet de Jest vers Vitest, on remplace jest. par vi., on ajoute les imports (ou l'option globals: true), et on supprime la configuration Babel devenue inutile.

Exercices

Exercice 1 sur 4 Écrire les tests d'une fonction Réussi À revoir

Voici la fonction lendemain de Prêt'Matos. Écris ses tests avec Vitest : au moins quatre cas bien choisis, dont les cas pièges des dates.

js
// src/rappels.js
export function lendemain(dateIso) {
  const date = new Date(`${dateIso}T00:00:00Z`);
  date.setUTCDate(date.getUTCDate() + 1);
  return date.toISOString().slice(0, 10);
}
Voir la réponseMasquer la réponse de l'exercice 1

Les classes intéressantes sont : un jour ordinaire, la fin d'un mois de 31 jours, la fin d'un mois de 30 jours, la fin de février (bissextile ou non) et la fin d'année.

js
import { test, expect } from 'vitest';
import { lendemain } from './rappels.js';

test.each([
  { cas: 'un jour ordinaire', date: '2027-03-13', attendu: '2027-03-14' },
  { cas: "la fin d'un mois de 31 jours", date: '2027-03-31', attendu: '2027-04-01' },
  { cas: "la fin d'un mois de 30 jours", date: '2027-04-30', attendu: '2027-05-01' },
  { cas: "le 28 février d'une année non bissextile", date: '2027-02-28', attendu: '2027-03-01' },
  { cas: "le 28 février d'une année bissextile", date: '2028-02-28', attendu: '2028-02-29' },
  { cas: "la fin d'année", date: '2027-12-31', attendu: '2028-01-01' },
])('le lendemain de $cas', ({ date, attendu }) => {
  expect(lendemain(date)).toBe(attendu);
});

Un cas de plus vaut la peine : le 28 mars 2027, nuit du passage à l'heure d'été. La fonction travaille en UTC, donc lendemain('2027-03-28') donne bien '2027-03-29' ; une version qui utiliserait l'heure locale et ajouterait 24 heures pourrait tomber à côté.

Tu avais trouvé ?

Exercice 2 sur 4 Choisir le matcher Réussi À revoir

Pour chaque vérification, donne le matcher le plus adapté.

  1. La réponse de l'API contient materiel: 'VP-02' et salarie: 3, parmi d'autres propriétés.
  2. verifierReservation renvoie exactement { acceptee: true }.
  3. Le total d'une note vaut 0,3 (calcul en virgule flottante).
  4. La liste des codes de matériel contient 'PC-07'.
  5. trouverPret(999) renvoie null.
  6. Le motif de refus contient les mots « déjà réservé ».
Voir la réponseMasquer la réponse de l'exercice 2
  1. toMatchObject({ materiel: 'VP-02', salarie: 3 }) : l'objet contient au moins ces propriétés.
  2. toEqual({ acceptee: true }) (ou toStrictEqual), pas toBe, qui comparerait les références.
  3. toBeCloseTo(0.3).
  4. toContain('PC-07').
  5. toBeNull().
  6. toMatch(/déjà réservé/) ou toContain('déjà réservé').

Tu avais trouvé ?

Exercice 3 sur 4 Le test toujours vert Réussi À revoir

Ce test est vert, et pourtant la méthode reservationsDu renvoie une liste vide à cause d'un bug. Trouve la cause et corrige le test.

js
test('retrouve la réservation de PC-07', () => {
  const depot = creerDepotMemoire(jeuDeTest());

  depot.reservationsDu('PC-07').then((reservations) => {
    expect(reservations).toHaveLength(1);
  });
});
Voir la réponseMasquer la réponse de l'exercice 3

La fonction de test n'attend pas la promesse : elle se termine immédiatement, avant que le then ne s'exécute. Vitest considère le test comme réussi, puisqu'aucune assertion n'a échoué pendant son exécution. On rend le test asynchrone et on attend le résultat :

js
test('retrouve la réservation de PC-07', async () => {
  const depot = creerDepotMemoire(jeuDeTest());

  const reservations = await depot.reservationsDu('PC-07');

  expect(reservations).toHaveLength(1);
});

Pour t'en assurer, fais échouer le test exprès (attends toHaveLength(5)) : la version corrigée passe au rouge, l'ancienne restait verte.

Tu avais trouvé ?

Exercice 4 sur 4 Migrer de Jest à Vitest Réussi À revoir

Convertis ce test Jest en test Vitest pour un projet en modules ES.

js
const { envoyerRappels } = require('./rappels');

test("n'envoie rien quand aucun prêt ne se termine demain", async () => {
  const depot = { pretsARendreLe: jest.fn().mockResolvedValue([]) };
  const messagerie = { envoyer: jest.fn() };

  await envoyerRappels({ depot, messagerie, journal: {}, aujourdhui: '2027-03-13' });

  expect(messagerie.envoyer).not.toHaveBeenCalled();
});
Voir la réponseMasquer la réponse de l'exercice 4
js
import { test, expect, vi } from 'vitest';
import { envoyerRappels } from './rappels.js';

test("n'envoie rien quand aucun prêt ne se termine demain", async () => {
  const depot = { pretsARendreLe: vi.fn().mockResolvedValue([]) };
  const messagerie = { envoyer: vi.fn() };

  await envoyerRappels({ depot, messagerie, journal: {}, aujourdhui: '2027-03-13' });

  expect(messagerie.envoyer).not.toHaveBeenCalled();
});

Trois changements : import au lieu de require (avec l'extension .js, obligatoire en modules ES), l'import de test, expect et vi depuis vitest, et vi.fn() à la place de jest.fn(). Le reste est identique.

Tu avais trouvé ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Tests unitaires en C# avec xUnitRevoir le sommaire du cours