Aller au contenu

Vitest : écrire des tests

import { afterEach, beforeEach, describe, expect, it } from 'vitest';
describe('Panier', () => {
let panier: Panier;
beforeEach(() => {
panier = new Panier();
});
afterEach(() => {
panier.vider();
});
it('démarre vide', () => {
expect(panier.total()).toBe(0);
});
it('ajoute des articles', () => {
panier.ajouter({ nom: 'Clavier', prixCentimes: 4900 });
expect(panier.total()).toBe(4900);
});
});
Fonction Rôle
describe(nom, fn) Regroupe des tests
it(nom, fn) / test(nom, fn) Un test (synonymes)
beforeAll, afterAll Une fois pour le groupe
beforeEach, afterEach Avant et après chaque test

Écrire des tests indépendants : chacun prépare son état (beforeEach) et ne dépend pas de l’ordre d’exécution.

expect(2 + 2).toBe(4); // égalité stricte (Object.is)
expect({ a: 1 }).toEqual({ a: 1 }); // égalité de valeur (objets, tableaux)
expect({ a: 1, b: undefined }).toStrictEqual({ a: 1, b: undefined }); // plus strict
expect([1, 2, 3]).toContain(2);
expect('bonjour monde').toMatch(/monde/);
expect(0.1 + 0.2).toBeCloseTo(0.3); // nombres décimaux
expect(valeur).toBeTruthy();
expect(valeur).toBeNull();
expect(valeur).toBeDefined();
expect(liste).toHaveLength(3);
expect(objet).toHaveProperty('nom', 'Alice');
expect(objet).toMatchObject({ nom: 'Alice' }); // sous-ensemble de propriétés
expect(nombre).toBeGreaterThan(3);
expect(() => fonction()).toThrow('message');
expect(fonction).not.toThrow();
Matcher Quand
toBe Primitives, identité d’objet
toEqual Comparer le contenu d’objets et tableaux
toStrictEqual Idem, en distinguant undefined et les classes
toMatchObject Vérifier seulement certains champs
toBeCloseTo Nombres à virgule
toThrow Erreur attendue (envelopper l’appel dans une fonction)

expect(a).toBe(b) sur deux objets distincts échoue même s’ils ont le même contenu : utiliser toEqual.

it('charge un client', async () => {
const client = await chargerClient(1);
expect(client.nom).toBe('Alice');
});
it('rejette si le client est inconnu', async () => {
await expect(chargerClient(999)).rejects.toThrow('introuvable');
});
it('résout avec un tableau', async () => {
await expect(listerClients()).resolves.toHaveLength(2);
});

Toujours attendre (await) les assertions sur des promesses : sans await, le test peut passer alors que l’assertion échoue après sa fin.

function diviser(a: number, b: number) {
if (b === 0) throw new RangeError('division par zéro');
return a / b;
}
it('refuse la division par zéro', () => {
expect(() => diviser(1, 0)).toThrow(RangeError);
expect(() => diviser(1, 0)).toThrow('division par zéro');
});
import { describe, expect, it } from 'vitest';
describe('estEmailValide', () => {
it.each([
['alice@exemple.com', true],
['alice@', false],
['@exemple.com', false],
['', false],
])('estEmailValide(%j) vaut %s', (entree, attendu) => {
expect(estEmailValide(entree)).toBe(attendu);
});
});

Avec des objets nommés :

it.each([
{ a: 1, b: 2, attendu: 3 },
{ a: -1, b: 1, attendu: 0 },
])('somme($a, $b) = $attendu', ({ a, b, attendu }) => {
expect(somme(a, b)).toBe(attendu);
});

Les formats %s (chaîne), %d (nombre), %j (JSON) et $champ construisent le nom de chaque cas.

it.only('seul ce test est exécuté', () => {}); // à ne jamais committer
it.skip('désactivé temporairement', () => {});
it.todo('à écrire : gérer les remises');
it.skipIf(process.platform === 'win32')('Linux seulement', () => {});
it.runIf(process.env.CI)('uniquement en intégration continue', () => {});
it.fails('échoue volontairement (bogue connu)', () => {
expect(1).toBe(2);
});

only laissé dans le code désactive silencieusement tous les autres tests : l’option allowOnly: false en CI le refuse (voir Configuration et intégration continue).

Depuis la ligne de commande :

Fenêtre de terminal
npx vitest run -t "ajoute des articles" # par nom de test
npx vitest run src/panier # par chemin
it('opération lente', async () => {
await operationLente();
}, 10_000); // délai maximal en ms (5 s par défaut)

Dans la configuration : testTimeout et hookTimeout.

  • Un test vérifie un comportement, nommé comme une phrase (it('refuse un email invalide')).
  • Structure organiser, agir, vérifier (arrange, act, assert).
  • Tester le comportement observable, pas les détails d’implémentation.
  • Pas d’accès au réseau ni au disque dans les tests unitaires (voir Mocks, espions et temps).
  • Données de test déterministes : pas d’aléatoire ni de date du jour sans contrôle du temps.
  • Un test qui échoue doit dire pourquoi : préférer toEqual avec les valeurs attendues à toBeTruthy.
Fenêtre de terminal
npm install -D supertest @types/supertest
import request from 'supertest';
import { describe, expect, it } from 'vitest';
import { app } from '../src/app';
describe('GET /sante', () => {
it('répond ok', async () => {
const reponse = await request(app).get('/sante');
expect(reponse.status).toBe(200);
expect(reponse.body).toEqual({ statut: 'ok' });
});
});

supertest démarre l’application sur un port éphémère (voir Express : API JSON pour séparer app et le démarrage).

Symptôme Piste
Un test passe alors qu’il devrait échouer Promesse non attendue (await manquant), assertion jamais atteinte
toBe échoue sur deux objets identiques Utiliser toEqual
Test timed out in 5000ms Opération non terminée : promesse jamais résolue, minuteur simulé non avancé
Tests dépendants de l’ordre État partagé entre tests : beforeEach pour réinitialiser
expected … to throw sans erreur Appel non enveloppé dans une fonction
Nom de test illisible avec each Formats %s, %j, $champ

Ubuntu 24.04 et 26.04 : différences à connaître

Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »

Aucune.