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
Crie um log de orçamento Claude Code antes do custo ficar confuso
Registre quem usou Claude Code, para qual trabalho e qual resultado gerou no time.
Checagem de 3 minutos antes do commit: confira o que o Claude Code mexeu antes de fechar
Roteiro de 3 minutos para flagrar antes do commit as mudanças que o Claude Code ampliou sozinho: escopo, diff e quais arquivos stagear.
Registro de riscos: o que montar antes de levar Claude Code para a equipe
Como montar um registro de riscos para levar Claude Code à equipe sem acidentes de permissão, CI e deploy. Com exemplos e código.