Atualização, setembro de 2026: leia isto primeiro se você está em um Angular recente. O Angular 21 traz o @angular/build:unit-test com o Vitest como runner padrão, e o time do Angular publica um guia de migração em https://angular.dev/guide/testing/migrating-to-vitest. No Angular 20 ou 21, use esse builder em vez do setup manual abaixo: você não precisa do plugin do Analog, nem de um vitest.config.ts, nem do seu próprio arquivo de test-setup. Tudo do Passo 1 ao Passo 5 aqui é a receita para o Angular 19 e anteriores, escrita em maio de 2025. As notas sobre a API de Jest para Vitest mais adiante valem nos dois casos.
Jest estava me custando tempo real. No MailHive, o produto SaaS que construo sozinho, a suite completa do front-end Angular levava bem mais de um minuto localmente e vários minutos no GitLab CI. Cada merge request tinha uma taxa de vários minutos só pra ver verde. Troquei para Vitest e cortei os dois a uma fração. Aqui está exatamente o que fiz.
Por Que Jest Fica Lento
Jest foi projetado para CommonJS e depende de Babel ou ts-jest para transpilar TypeScript antes de rodar qualquer coisa. Essa transformação acontece em cada execução fria. Vitest é construído sobre Vite, que usa esbuild para transformações e ESM nativo. Também roda arquivos spec em paralelo por padrão (processos filhos desde o Vitest 2, worker threads se você optar) e mantém o module graph do Vite para rodar apenas testes afetados pelas suas mudanças no modo watch. A diferença é estrutural, não cosmética.
Passo 1: Trocar as Dependências
# Remove Jest
npm uninstall jest jest-environment-jsdom @types/jest ts-jest jest-preset-angular
# Install Vitest + Angular plugin
npm install --save-dev vitest @vitest/coverage-v8 jsdom @analogjs/vite-plugin-angularO pacote @analogjs/vite-plugin-angular trata a compilação Angular dentro do Vite. Sem ele, o compilador de templates e os decorators do Angular não funcionam no ambiente Vitest.
Passo 2: vitest.config.ts
import { defineConfig } from 'vitest/config';
import angular from '@analogjs/vite-plugin-angular';
export default defineConfig({
plugins: [angular()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['src/test-setup.ts'],
include: ['src/**/*.spec.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'lcov'],
include: ['src/app/**/*.ts'],
exclude: ['src/app/**/*.spec.ts', 'src/app/**/*.module.ts']
}
}
});globals: true é importante. Ele torna describe, it, expect e vi disponíveis sem imports, correspondendo à API do Jest que seus testes existentes esperam.
Passo 3: Substituir o Arquivo de Setup de Testes
Delete setup-jest.ts ou src/test.ts. Substitua por um src/test-setup.ts limpo que inicializa o TestBed do Angular.
// src/test-setup.ts
import '@angular/compiler';
import 'zone.js';
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);Uma nota de versão sobre esse arquivo: @angular/platform-browser-dynamic/testing era o import correto em maio de 2025 e foi substituído a partir do Angular 20. No Angular 20 em diante, use BrowserTestingModule e platformBrowserTesting de @angular/platform-browser/testing. A chamada initTestEnvironment é idêntica no resto.
Passo 4: Atualizar tsconfig.spec.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": ["vitest/globals"]
},
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}Remova @types/jest de types se ainda estiver lá. Ter ambos causa conflitos de tipo.
Passo 5: Atualizar Scripts do package.json
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
}
}O Custo da Migração: API do Jest para Vitest
O trabalho real da migração é um find-and-replace global nos seus arquivos spec. As APIs são quase idênticas por design.
- jest.fn() vira vi.fn(). Faça um replace global no projeto de 'jest.' para 'vi.'
- jest.mock() vira vi.mock(). O comportamento de hoisting é ligeiramente diferente, veja a documentação do Vitest sobre hoisting se você usa factory functions
- Os snapshots em geral continuam valendo. O Vitest escreve um cabeçalho de arquivo diferente e formata alguns casos de outro jeito (snapshots de erro, separadores de mensagem customizados), então espere alguns diffs. Regenerá-los uma vez depois da migração é a correção mais simples
- @testing-library/jest-dom matchers precisam do import do vitest: import '@testing-library/jest-dom/vitest'
- Zone.js ainda precisa ser importado no seu arquivo de setup. O TestBed do Angular espera por ele
- fakeAsync, tick e TestBed são do Angular, não do Jest. Funcionam sem mudanças
Como Foi em um Projeto Real
O projeto era o front-end Angular do MailHive: principalmente testes de serviço com uma boa parcela de testes de componente usando TestBed, tudo isso bloqueando merges no GitLab CI atrás de um limite de cobertura.
- Jest com ts-jest (cold): bem mais de um minuto localmente
- Vitest (cold): questão de segundos
- Vitest (cache quente): re-execuções quase instantâneas no watch mode
- GitLab CI shared runner, Jest: vários minutos por pipeline
- GitLab CI shared runner, Vitest: uma fração disso, rápido o suficiente para o job de testes deixar de ser o gargalo
Essa melhoria no CI não é só um número. Significa que o feedback chega antes de você trocar de contexto. Merge requests andam mais rápido. Mesmo sozinho, você para de temer o job de teste.
Uma Pegadinha Que Vale Destacar
Se algum teste ainda faz spy em Date ou Math.random com jest.spyOn, por exemplo em um helper de teste compartilhado que não é arquivo spec e escapou do find-and-replace, ele vai falhar porque não existe global jest no Vitest. Substitua por vi.spyOn. Além disso, vi.useFakeTimers() funciona igual a jest.useFakeTimers() mas você precisa chamar vi.useRealTimers() no afterEach se ainda não está fazendo. Tive alguns testes que começaram a falhar intermitentemente até rastrear isso.
Testes rápidos mudam comportamento. Quando o loop de feedback é instantâneo, desenvolvedores rodam testes antes de commitar. Quando leva minutos, eles fazem push e torcem.
Reserve uma tarde para um projeto Angular típico. A maior parte disso é o find-and-replace de API e caçar um punhado de edge cases. O retorno se acumula em cada execução de testes dali pra frente.
Referências
- Vitest documentation. https://vitest.dev/guide/
- Analog vite-plugin-angular. https://analogjs.org/docs/packages/vite-plugin-angular