Guia avançado de Vitest com Claude Code
Configure Vitest com Claude Code: dublês de teste, timers falsos, jsdom, cobertura, instantâneos e CI.
O que este fluxo de Vitest resolve
Pedir ao Claude Code “adicione testes Vitest” é pouco. Os testes podem passar localmente e falhar quando envolvem tempo, DOM, APIs externas ou CI. Este guia organiza essas áreas de risco em um fluxo prático: dublês de teste para substituir dependências, timers falsos para controlar o relógio, cobertura para revelar ramos sem verificação, jsdom para estrutura de DOM, instantâneos pequenos para contratos de renderização e comandos de CI que encerram corretamente.
Em 3 de junho de 2026, revisei a documentação oficial do Vitest: Getting Started, Mocking, Timers, Dates, Test Environment, Coverage, Snapshot e CLI. A documentação do Vitest 4 diferencia o modo de observação de vitest run; em CI, essa diferença evita jobs que nunca terminam.
Use o Claude Code como parceiro de projeto de testes. Informe qual fronteira deve ser simulada, se o relógio deve ser fixo, se jsdom basta e qual comando comprova o resultado. Para contexto adicional, leia estratégias de teste com Claude Code, guia de MSW para API e testes E2E com Playwright.
flowchart TD
A["Especificação: sucesso e falhas"] --> B["Vitest config: node/jsdom/coverage"]
B --> C["Unidade: lógica pura e fronteiras API"]
B --> D["Tempo: timers falsos e Date fixo"]
B --> E["DOM: jsdom e instantâneos"]
C --> F["CI: vitest run --coverage"]
D --> F
E --> F
Comece por uma configuração estável
Instale Vitest, o provedor de cobertura V8, jsdom e TypeScript. Em uma aplicação Vite, a configuração pode ser compartilhada, mas um vitest.config.ts dedicado deixa a intenção clara para Claude Code e revisores.
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 mantém os imports explícitos. Isso ajuda quando o Claude Code move testes entre arquivos. restoreMocks: true reduz vazamento de dublês, mas não restaura timers falsos nem limpa o DOM.
Caso 1: Simular uma fronteira de API
Teste unitário não deve chamar uma API real de pedidos, pagamentos ou usuários. Verifique o contrato sob seu controle: rota, corpo, validação de entrada e tradução de erro.
// 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",
);
});
});
Esse estilo de injeção de dependência costuma ser mais claro do que substituir um módulo inteiro. vi.mock() é útil, mas o Vitest o eleva antes dos imports; inicialização fora de ordem confunde iniciantes e testes gerados por IA.
Caso 2: Fixar tempo com timers falsos
Períodos de teste, tentativas, notificações e antirrebote ficam instáveis quando esperam tempo real. O Vitest controla setTimeout, setInterval e a data do sistema.
// 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);
});
});
O erro comum é esquecer vi.useRealTimers(). Um relógio falso deixado por um arquivo pode quebrar outro teste. Quando houver Promises, use await. Limites de data e fuso horário aparecem em tratamento de data e hora com Claude Code.
Caso 3: Proteger DOM com jsdom e instantâneos
jsdom imita APIs de DOM dentro do Node. Ele serve para estrutura, texto e atributos de acessibilidade. Não substitui navegador real para layout, foco, Canvas ou regressão visual.
// 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, "Salvo");
expect(notice.getAttribute("role")).toBe("status");
expect(notice.textContent).toBe("Salvo");
expect({
html: document.body.innerHTML,
text: notice.textContent,
}).toMatchInlineSnapshot(`
{
"html": "<div id=\\"app\\"><p role=\\"status\\" data-testid=\\"notice\\">Salvo</p></div>",
"text": "Salvo",
}
`);
});
});
Instantâneos devem ser pequenos. Atributos importantes ficam em expect direto; o instantâneo guarda apenas uma estrutura compacta. Comportamento real de navegador deve ir para Playwright.
Cobertura e CI
Cobertura serve para revelar ramos não testados, não para inflar porcentagem. O Vitest documenta provedores V8 e Istanbul, com V8 como padrão. Defina coverage.include; caso contrário, arquivos novos nunca importados pelos testes podem sumir do relatório.
# .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
Em CI, use vitest run. Apenas vitest pode entrar em modo de observação. O desenho maior está em CI/CD com Claude Code.
Prompt prático para Claude Code
Adicione testes Vitest para src/orders.ts.
Teste somente createOrder.
Simule a API externa com vi.fn(); não faça chamadas HTTP reais.
Inclua sucesso, entrada inválida e falha de transporte.
Não use timers falsos nem jsdom salvo se o código exigir.
Depois da edição, informe o comando esperado npm run test:run e os riscos restantes.
Esse prompt define escopo, fronteira simulada, falhas obrigatórias e comando de prova. Coloque regras semelhantes em boas práticas de CLAUDE.md.
Falhas comuns
| Falha | Sintoma | Correção |
|---|---|---|
| Dublês não restaurados | Contagem de chamadas ou implementação falsa vaza | Usar restoreMocks, vi.clearAllMocks() ou vi.restoreAllMocks() conforme o caso |
| Timers falsos não restaurados | Testes de tempo falham em outro arquivo | Chamar vi.useRealTimers() em afterEach |
| Tratar jsdom como navegador real | CSS, layout, imagens ou Canvas diferem | Vitest para contrato DOM, Playwright para navegador |
| Instantâneo grande demais | Revisão cheia de ruído | Guardar apenas estruturas pequenas |
Falta coverage.include | Arquivos sem teste ficam invisíveis | Incluir src/**/*.{ts,tsx} explicitamente |
| Assíncrono sem espera | Falsos positivos | Usar await expect(promise).resolves ou rejects |
| CI em modo de observação | Job não termina | Usar vitest run ou vitest related --run |
Para adaptar esse fluxo ao seu repositório, a ClaudeCodeLab oferece uma página de treinamento em inglês e modelos práticos para padrões de teste, prompts de revisão e portas de CI.
Resultado prático
O resultado é uma base Vitest com três casos copiáveis: fronteira de API, tempo fixo e renderização jsdom com instantâneo pequeno. Antes de publicar, revisei documentos oficiais, links internos, links externos, blocos de código, updatedDate, cobertura e o uso de vitest run em CI.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Permission receipt no Claude Code: escopo, prova e rollback
Padrão de permission receipt para Claude Code: ações permitidas, limites de aprovação, comandos de prova, rollback e CTA de receita.
Agent Harness seguro para Claude Code e Codex: permissoes, verificacao e rollback
Monte uma base segura para agentes com Claude Code e Codex usando politicas, plano, verificacao e recuperacao.
Subagentes no Claude Code: guia prático para delegar trabalho com segurança
Guia prático de subagentes no Claude Code para dividir artigos e código: regras, prompts, riscos e checklist.