Guide avancé Vitest avec Claude Code
Concevoir avec Claude Code des tests Vitest: doublures, faux minuteurs, jsdom, couverture, instantanés et CI.
Le problème que ce flux Vitest règle
Demander simplement à Claude Code “ajoute des tests Vitest” produit souvent des tests qui passent en local mais cassent autour du temps, du DOM, des API externes ou de la CI. Ce guide transforme ces zones risquées en un flux pratique: doublures de test pour remplacer les dépendances, faux minuteurs pour contrôler l’horloge, couverture pour repérer les branches non testées, jsdom pour la structure DOM, instantanés courts pour les contrats de rendu, et commandes CI qui se terminent vraiment.
Le 3 juin 2026, j’ai vérifié la documentation officielle Vitest: Getting Started, Mocking, Timers, Dates, Test Environment, Coverage, Snapshot et CLI. La documentation de Vitest 4 distingue le mode surveillance de vitest run; cette différence compte beaucoup en CI.
Avec Claude Code, donnez le périmètre, la frontière à simuler, l’horloge à fixer ou non, l’environnement jsdom éventuel, et la commande de preuve. Pour compléter, lisez aussi stratégies de test avec Claude Code, guide MSW pour les API et tests E2E Playwright.
flowchart TD
A["Spécification: succès et échecs"] --> B["Vitest config: node/jsdom/coverage"]
B --> C["Unitaire: logique pure et frontières API"]
B --> D["Temps: faux minuteurs et Date fixe"]
B --> E["DOM: jsdom et instantanés"]
C --> F["CI: vitest run --coverage"]
D --> F
E --> F
Commencer par une configuration stable
Installez Vitest, le fournisseur de couverture V8, jsdom et TypeScript. Une application Vite peut partager sa configuration, mais un vitest.config.ts dédié rend l’intention plus lisible pour Claude Code et pour les revues.
npm install -D vitest @vitest/coverage-v8 jsdom typescript
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"coverage": "vitest run --coverage"
}
}
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "node",
globals: false,
restoreMocks: true,
coverage: {
provider: "v8",
reporter: ["text", "html"],
include: ["src/**/*.{ts,tsx}"],
exclude: ["src/**/*.d.ts", "src/**/*.test.{ts,tsx}", "src/test/**"],
thresholds: {
lines: 80,
functions: 80,
branches: 75,
statements: 80,
},
},
},
});
globals: false force les imports explicites de describe et expect, ce qui réduit les surprises quand Claude Code déplace un test. restoreMocks: true aide, mais ne réinitialise ni les faux minuteurs ni le DOM.
Cas 1: Simuler une frontière API
Un test unitaire ne doit pas appeler une vraie API de commande, paiement ou utilisateur. Testez le contrat que vous maîtrisez: chemin, corps, validation d’entrée et traduction d’erreur.
// src/orders.ts
export type ApiClient = {
post<T>(path: string, body: unknown): Promise<T>;
};
export class OrderError extends Error {
constructor(message = "Order request failed") {
super(message);
this.name = "OrderError";
}
}
type OrderInput = {
sku: string;
quantity: number;
};
type OrderResponse = {
id: string;
status: "accepted" | "queued";
};
export async function createOrder(api: ApiClient, input: OrderInput) {
if (input.quantity < 1) {
throw new OrderError("Quantity must be at least 1");
}
try {
return await api.post<OrderResponse>("/orders", input);
} catch {
throw new OrderError("Order API failed");
}
}
// src/orders.test.ts
import { describe, expect, it, vi } from "vitest";
import { createOrder, type ApiClient, OrderError } from "./orders";
describe("createOrder", () => {
it("posts the order payload to the API", async () => {
const api: ApiClient = {
post: vi.fn().mockResolvedValue({ id: "ord_1", status: "accepted" }),
};
await expect(createOrder(api, { sku: "book-1", quantity: 2 })).resolves.toEqual({
id: "ord_1",
status: "accepted",
});
expect(api.post).toHaveBeenCalledWith("/orders", { sku: "book-1", quantity: 2 });
});
it("rejects invalid quantity before calling the API", async () => {
const api: ApiClient = { post: vi.fn() };
await expect(createOrder(api, { sku: "book-1", quantity: 0 })).rejects.toBeInstanceOf(
OrderError,
);
expect(api.post).not.toHaveBeenCalled();
});
it("wraps transport errors in a domain error", async () => {
const api: ApiClient = {
post: vi.fn().mockRejectedValue(new Error("ECONNRESET")),
};
await expect(createOrder(api, { sku: "book-1", quantity: 1 })).rejects.toThrow(
"Order API failed",
);
});
});
Cette injection de dépendance est souvent plus claire qu’un remplacement de module complet. vi.mock() reste utile, mais Vitest le hisse avant les imports; un mauvais ordre d’initialisation devient vite difficile à lire.
Cas 2: Fixer le temps avec de faux minuteurs
Essais gratuits, relances, notifications et anti-rebond deviennent instables si le test attend le temps réel. Vitest permet de contrôler setTimeout, setInterval et la date système.
// src/trial.ts
const DAY_MS = 24 * 60 * 60 * 1000;
export function getTrialEndsAt(days = 7) {
return new Date(Date.now() + days * DAY_MS).toISOString();
}
export function scheduleTrialReminder(send: () => void, days = 7) {
return setTimeout(send, days * DAY_MS);
}
// src/trial.test.ts
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { getTrialEndsAt, scheduleTrialReminder } from "./trial";
describe("trial reminder", () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-06-03T00:00:00.000Z"));
});
afterEach(() => {
vi.useRealTimers();
});
it("calculates the trial end date from the fixed clock", () => {
expect(getTrialEndsAt()).toBe("2026-06-10T00:00:00.000Z");
});
it("runs the reminder after the configured number of days", () => {
const send = vi.fn();
const timer = scheduleTrialReminder(send, 3);
vi.advanceTimersByTime(3 * 24 * 60 * 60 * 1000 - 1);
expect(send).not.toHaveBeenCalled();
vi.advanceTimersByTime(1);
expect(send).toHaveBeenCalledTimes(1);
clearTimeout(timer);
});
});
Le piège classique est d’oublier vi.useRealTimers(). Une horloge fausse laissée par un fichier peut faire échouer un autre test. Quand une promesse est impliquée, utilisez aussi await. Les limites de date et fuseau horaire sont détaillées dans gestion des dates avec Claude Code.
Cas 3: Protéger le DOM avec jsdom et instantanés
jsdom imite les API DOM dans Node. Il convient à la structure, au texte et aux attributs d’accessibilité. Il ne remplace pas un vrai navigateur pour la mise en page, le focus réel, Canvas ou les régressions visuelles.
// src/notice.ts
export function renderNotice(target: HTMLElement, message: string) {
target.innerHTML = "";
const notice = document.createElement("p");
notice.setAttribute("role", "status");
notice.dataset.testid = "notice";
notice.textContent = message;
target.append(notice);
return notice;
}
// src/notice.test.ts
// @vitest-environment jsdom
import { afterEach, describe, expect, it } from "vitest";
import { renderNotice } from "./notice";
afterEach(() => {
document.body.innerHTML = "";
});
describe("renderNotice", () => {
it("renders an accessible status message", () => {
document.body.innerHTML = '<div id="app"></div>';
const target = document.querySelector<HTMLDivElement>("#app");
if (!target) throw new Error("missing #app");
const notice = renderNotice(target, "Enregistré");
expect(notice.getAttribute("role")).toBe("status");
expect(notice.textContent).toBe("Enregistré");
expect({
html: document.body.innerHTML,
text: notice.textContent,
}).toMatchInlineSnapshot(`
{
"html": "<div id=\\"app\\"><p role=\\"status\\" data-testid=\\"notice\\">Enregistré</p></div>",
"text": "Enregistré",
}
`);
});
});
Un instantané doit rester court. Vérifiez les attributs importants avec expect, puis gardez une petite structure en instantané. Pour l’interaction réelle et le rendu visuel, utilisez Playwright.
Couverture et CI
La couverture sert à trouver les branches non vérifiées, pas à fabriquer un joli pourcentage. Vitest documente les fournisseurs V8 et Istanbul, avec V8 comme fournisseur par défaut. Ajoutez coverage.include, sinon un fichier jamais importé par les tests peut disparaître du rapport.
# .github/workflows/vitest.yml
name: vitest
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run test:run
- run: npm run coverage
En CI, utilisez vitest run. La commande vitest seule peut ouvrir le mode surveillance. Le flux complet est traité dans configuration CI/CD avec Claude Code.
Prompt pratique pour Claude Code
Ajoute des tests Vitest pour src/orders.ts.
Teste seulement createOrder.
Simule l'API externe avec vi.fn(); ne fais aucun appel HTTP réel.
Inclue les cas succès, entrée invalide et échec de transport.
N'utilise pas de faux minuteurs ni jsdom sauf si le code l'exige.
Après modification, indique la commande attendue npm run test:run et les risques restants.
Ce prompt fournit le périmètre, la frontière simulée, les échecs obligatoires et la preuve attendue. Ajoutez la même règle aux bonnes pratiques CLAUDE.md.
Échecs fréquents
| Échec | Symptôme | Correction |
|---|---|---|
| Doublures non restaurées | Appels ou fausses implémentations fuient | Utiliser restoreMocks, vi.clearAllMocks() ou vi.restoreAllMocks() selon le besoin |
| Faux minuteurs non restaurés | Un autre fichier échoue de façon aléatoire | Appeler vi.useRealTimers() dans afterEach |
| jsdom pris pour un navigateur réel | CSS, mise en page, images ou Canvas diffèrent | Vitest pour le contrat DOM, Playwright pour le navigateur |
| Instantané trop large | La revue devient bruyante | Ne sauvegarder que de petites structures |
coverage.include absent | Des fichiers non testés restent invisibles | Inclure explicitement src/**/*.{ts,tsx} |
| Asynchrone non attendu | Faux positifs | Utiliser await expect(promise).resolves ou rejects |
| CI en mode surveillance | Le travail ne se termine pas | Utiliser vitest run ou vitest related --run |
Pour adapter ce flux à votre dépôt, ClaudeCodeLab propose une page de formation en anglais et des modèles pratiques pour les standards de test, les prompts de revue et les portes CI.
Résultat vérifié
Le résultat est une base Vitest avec trois cas copiables: frontière API, temps fixé, et rendu jsdom avec petit instantané. Avant publication, j’ai vérifié les documents officiels Vitest, les liens internes, les liens externes, les blocs de code, updatedDate, la couverture et l’usage de vitest run en CI.
PDF gratuit: cheatsheet Claude Code
Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.
Nous protégeons vos données et n'envoyons pas de spam.
À propos de l'auteur
Masa
Ingénieur spécialisé dans les workflows pratiques avec Claude Code.
Articles liés
Permission receipt Claude Code : portée, preuves et rollback
Modèle de permission receipt pour Claude Code : actions autorisées, limites d'approbation, commandes de preuve, rollback et CTAs revenus.
Agent Harness securise pour Claude Code et Codex : permissions, verification et rollback
Construisez un Agent Harness pratique pour Claude Code et Codex avec politiques, plan, verification et recuperation.
Sous-agents Claude Code : guide pratique pour déléguer sans perdre le contrôle
Guide pratique des sous-agents Claude Code pour répartir articles et code : règles, prompts, pièges et checklist.