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 22 de julho de 2026, este artigo foi conferido com o guia CLI do Vitest, a configuração de watch e o guia de migração do Vitest 4. O Vitest 4 exige Vite 6 ou mais recente e Node 20 ou mais recente. Em CI ou terminal não interativo, vitest muda automaticamente para execução única; vitest run ainda deixa a intenção do script explícita.
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, escreva vitest run explicitamente. vitest só observa quando CI é false e o terminal é interativo; em CI ou terminal não interativo, executa uma vez. Para ferramentas interativas como lint-staged, use vitest related --run. 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.
Três casos de uso
Caso de uso 1: formulário de pedido ou cadastro. Uma rota ou corpo errado pode impedir uma compra. O Claude Code pode escrever testes de fronteira com vi.fn() para sucesso, entrada inválida e falha de transporte; uma pessoa deve aprovar pagamento, cobrança, estoque e regras de dados de clientes.
Caso de uso 2: lembretes de teste, consulta ou renovação. O Claude Code pode fixar Date, avançar timers falsos e provar que o callback roda uma única vez. Fuso horário, dias úteis, envio real de email ou SMS e impactos de cobrança precisam de revisão humana.
Caso de uso 3: contratos DOM pequenos, como aviso salvo, mensagem de validação ou banner administrativo. O Claude Code pode verificar texto, papéis e instantâneo pequeno com jsdom. Comportamento em navegador real, foco, regressão visual e texto visível para clientes exigem aprovação humana.
O que delegar ao Claude Code e o que aprovar
| Área | Pode delegar ao Claude Code | Exige aprovação humana |
|---|---|---|
| Código de teste | Dublês, timers falsos, asserções jsdom e comandos de cobertura | Confirmar que as asserções não enfraquecem o comportamento |
| CI | Sugerir vitest run e vitest run --coverage | Ativar checks obrigatórios, runners com custo ou produção |
| Dados | Usar IDs sintéticos como ord_1 e book-1 | Revisar dados de clientes, segredos, API keys e IDs de pagamento |
| Navegador | Manter contratos DOM no Vitest | Validar fluxos reais com Playwright ou revisão manual |
Armadilhas e correções
| 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 |
| Runner interativo entra em observação | Processo espera alterações | Usar vitest run; para arquivos alterados, vitest related --run |
Corrigir não significa deixar tudo verde a qualquer custo. Quando um teste falhar, cole o erro completo, peça ao Claude Code para não apagar asserções e exija que ele corrija implementação ou configuração do mock primeiro. Segurança, produção, cobrança e dados de clientes continuam dependendo de revisão humana do diff.
Para aplicar as mesmas regras de revisão no repositório, use os modelos práticos da ClaudeCodeLab para padrões de teste, prompts de revisão e portas de CI.
Resultado prático
Em 22 de julho de 2026, os exemplos TypeScript foram extraídos para um projeto temporário e executados com Node v24.14.1 e Vitest 4.1.10. vitest run e vitest run --coverage aprovaram 4 arquivos e 7 testes de fronteira de API, mock de módulo, relógio fixo, limite de timer, atributos DOM e snapshot inline. A cobertura foi 94,73% statements, 100% branches, 85,71% functions e 94,73% lines. O mailer foi simulado; nenhum e-mail real nem API de produção foi chamado.
Artigos relacionados
Testes E2E com Claude Code e Playwright em produção
Use Claude Code e Playwright para E2E, mobile screenshots, auth state, Trace Viewer, seletores e retries no CI.
Tipos utilitários do TypeScript com Claude Code: guia prático
Aprenda Pick, Omit, Partial, Record, ReturnType e Awaited com exemplos executáveis para Claude Code.
Error Boundaries React com Claude Code: guia seguro de implementacao
Implemente Error Boundaries React com Claude Code: escopo, posicionamento, reset, logs seguros, testes e prompts.
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.