O que é o Vitest?
O Vitest é um test runner alimentado pelo Vite. Ele reutiliza a sua configuração existente do Vite — aliases, plugins, transformações de TypeScript e JSX — para que os testes sejam executados através do mesmo pipeline da sua aplicação. Essa base compartilhada é o motivo pelo qual ele inicia rápido e permanece rápido.
A API parecerá familiar se você já utilizou o Jest. describe, it, expect e os helpers de mocking funcionam da mesma maneira. O que muda é o motor por baixo: o Vitest executa testes em workers paralelos, monitora arquivos com o grafo de módulos do Vite e oferece suporte nativo ao TypeScript sem a necessidade de configurações extras.
Escrevendo seu primeiro teste
Um arquivo de teste importa de vitest e descreve o comportamento que ele verifica.
// sum.test.ts
import { describe, it, expect } from "vitest";
import { sum } from "./sum";
describe("sum", () => {
it("adds two numbers", () => {
expect(sum(2, 3)).toBe(5);
});
it("handles negatives", () => {
expect(sum(-1, -1)).toBe(-2);
});
});
Nomeie os testes de acordo com o comportamento, não com a função. Um teste que falha e diz “soma dois números” informa o que quebrou; um que diz “teste de soma 1” não informa. Agrupe casos relacionados com describe e use beforeEach e afterEach para setup e cleanup.
Assertions
O Vitest oferece uma API de matchers robusta, muito similar à do Jest.
// assertions.test.ts
expect(value).toBe(5); // strict equality
expect(value).toEqual({ a: 1 }); // deep equality
expect(value).toBeTruthy();
expect(list).toHaveLength(3);
expect(list).toContain("a");
expect(fn).toThrow("invalid");
expect(value).toBeGreaterThan(3);
Use toEqual para objetos e arrays e toBe para primitivos e verificações de referência. toMatchObject e toContainEqual são úteis quando você se importa apenas com parte de uma estrutura.
Mocks e spies
O isolamento é fundamental para os testes unitários. O Vitest fornece vi.fn para mocks, vi.spyOn para envolver métodos existentes e vi.mock para substituir módulos inteiros.
// api.test.ts
import { vi, describe, it, expect } from "vitest";
import { fetchUser } from "./api";
vi.mock("./http", () => ({
get: vi.fn().mockResolvedValue({ id: 1, name: "Ada" }),
}));
describe("fetchUser", () => {
it("returns the user", async () => {
await expect(fetchUser(1)).resolves.toEqual({ id: 1, name: "Ada" });
});
});
vi.mock é içada (hoisted) acima dos imports, portanto, o mock é registrado antes que o módulo sob teste o carregue. Quando a factory precisar de uma variável definida no arquivo de teste, use vi.hoisted para que o valor exista antes da execução do mock.
Testes assíncronos
Aguarde a operação e faça a asserção do resultado. O Vitest fornece os helpers resolves e rejects para promises.
// async.test.ts
it("rejects on failure", async () => {
await expect(loadUser(-1)).rejects.toThrow("Invalid id");
});
Prefira este estilo em vez do callback done. Esquecer o done causa timeouts difíceis de diagnosticar, e rejections não tratadas podem vazar entre os testes. Para timers falsos, vi.useFakeTimers() e vi.advanceTimersByTime() permitem que você teste debounces e intervalos de forma determinística.
Snapshots
Snapshots capturam um valor e o comparam em execuções posteriores. Eles são úteis para saídas grandes e estáveis, como dados serializados ou marcações renderizadas, mas são fáceis de usar incorretamente.
// snapshot.test.ts
it("matches the shape", () => {
expect(createConfig({ debug: true })).toMatchSnapshot();
});
Um snapshot que é atualizado sem ser revisado é pior do que a ausência de um teste. Use-os para estruturas de dados onde uma comparação completa seja impraticável, e prefira asserções explícitas quando você souber o que é relevante. toMatchInlineSnapshot mantém o valor esperado no arquivo de teste, o que torna as revisões significativas.
Configuração
O Vitest lê um bloco test do seu config do Vite ou de um vitest.config.ts dedicado.
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "jsdom",
globals: true,
coverage: {
provider: "v8",
reporter: ["text", "html"],
thresholds: { lines: 80 },
},
},
});
A opção environment seleciona node, jsdom, happy-dom ou um browser. Para testes de componentes, jsdom ou happy-dom são típicos; para lógica pura, mantenha o ambiente rápido node. Os coverage thresholds transformam uma meta de cobertura em uma regra obrigatória.
Vitest e a pirâmide de testes
O Vitest é a camada de testes unitários e de integração. Combine-o com uma biblioteca de testes de componentes, como a Testing Library, para o comportamento da UI, e com uma ferramenta de ponta a ponta (end-to-end) como o Playwright para fluxos completos de usuário. Juntos, eles cobrem a pirâmide: muitos testes unitários rápidos, menos testes de integração e alguns poucos testes end-to-end.
Melhores práticas
- Teste o comportamento, não os detalhes de implementação.
- Mantenha cada teste focado em apenas um comportamento.
- Use
beforeEachpara configurações compartilhadas e limpe mocks comvi.clearAllMocks. - Prefira
resolveserejectsem vez de callbacksdone. - Faça mock nas fronteiras (rede, tempo, aleatoriedade), não em cada chamada interna.
- Use limites de cobertura (coverage thresholds) para evitar regressões silenciosas.
- Execute em modo watch durante o desenvolvimento e no CI a cada push.
Erros comuns
- Fazer asserções em estados internos ou métodos privados em vez de comportamentos observáveis.
- Atualizar snapshots sem revisar o diff.
- Esquecer de limpar mocks entre os testes, causando vazamento de estado.
- Usar timers reais, tornando os testes lentos e instáveis (flaky).
- Exagerar nos mocks a ponto de o teste verificar apenas os próprios mocks.
- Testar o mesmo comportamento em todos os níveis da pirâmide.
Próximos passos
O Vitest é a maneira mais rápida de começar a testar um projeto Vite. Compare-o com o clássico Jest, adicione Testing Library para componentes e Playwright para fluxos end-to-end, e utilize o Vite para manter a configuração compartilhada. Depois, escreva alguns testes para o código que você já possui e veja como um refactor se torna seguro.