Advanced (Atualizado: 03/06/2026)

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.

Guia avançado de Vitest com Claude Code

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

FalhaSintomaCorreção
Dublês não restauradosContagem de chamadas ou implementação falsa vazaUsar restoreMocks, vi.clearAllMocks() ou vi.restoreAllMocks() conforme o caso
Timers falsos não restauradosTestes de tempo falham em outro arquivoChamar vi.useRealTimers() em afterEach
Tratar jsdom como navegador realCSS, layout, imagens ou Canvas diferemVitest para contrato DOM, Playwright para navegador
Instantâneo grande demaisRevisão cheia de ruídoGuardar apenas estruturas pequenas
Falta coverage.includeArquivos sem teste ficam invisíveisIncluir src/**/*.{ts,tsx} explicitamente
Assíncrono sem esperaFalsos positivosUsar await expect(promise).resolves ou rejects
CI em modo de observaçãoJob não terminaUsar 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.

#Claude Code #Vitest #testes #TypeScript #garantia de qualidade
Grátis

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.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.