Vitest vs Jest em CI: velocidade, ESM e migração
O tempo de execução dos testes costuma ser a etapa mais longa do CI - Vitest e Jest diferem bastante em velocidade e ESM.
Ambos rodam testes JS/TS; o Vitest é nativo do Vite e ESM-first, o Jest é o padrão consolidado com um ecossistema enorme.
Vitest vs Jest at a glance
| Jest | Vitest | |
|---|---|---|
| Suporte a ESM | Desajeitado (transforms) | Nativo |
| Velocidade | Boa | Muitas vezes mais rápido (transform do Vite) |
| Configuração | Madura, muitos presets | Mínima, alinhada ao Vite |
| Ecossistema | O maior | Crescendo, API compatível com Jest |
| Configuração | Reaproveita o vite.config.ts | Configuração e cadeia de transform próprias do Jest |
| Asserções | Chai com expect compatível com Jest | expect |
| Watch mode | Reexecução no estilo HMR | Watch padrão |
| Cobertura | v8 ou Istanbul | babel-plugin-istanbul ou v8 |
| Modo navegador | Sim | Não (apenas simulação jsdom/happy-dom) |
| Benchmark / teste de tipos | Tinybench, expect-type | Não |
| Sharding | Sim | Sim, --shard desde a v28 |
Em CI
O Vitest tende a ser mais rápido e evita as dores de cabeça de transform de ESM que causam as falhas "Cannot use import statement" do Jest. O Jest continua sendo a escolha segura para suítes grandes e maduras.
- Static
importstatements are evaluated before your code runs, so the hoistingjest.mock()depends on cannot happen under ESM. require()of an ESM file with top-level await throwsERR_REQUIRE_ASYNC_MODULE.- The
jestobject has to come from@jest/globalsorimport.meta.jestrather than being ambient. - Jest documentation notes the underlying Node APIs it uses for this are themselves experimental.
# Jest, ESM mode
NODE_OPTIONS="$NODE_OPTIONS --experimental-vm-modules" npx jest
# ...plus transform: {} in config (or a transformer emitting ESM),
# ...plus extensionsToTreatAsEsm for .ts/.jsx,
# ...and jest.mock() no longer works:
const { charge } = await import('./payments.js');
jest.unstable_mockModule('./payments.js', () => ({ charge: jest.fn() }));
# Vitest, no flags, no mode
vi.mock('./payments.js', () => ({ charge: vi.fn() }));Ambos podem estourar a memória
Suítes grandes estouram a memória dos workers de CI em qualquer runner - limite os workers e dimensione a memória de qualquer forma.
| Approach | Type-checks tests? | Cost |
|---|---|---|
| Jest + babel-jest | No | Fast, but type errors in tests go unnoticed |
| Jest + ts-jest | Yes | Noticeably slower; type-checks on every run |
| Vitest | Via your existing tsconfig | No extra transform config |
Por que a migração é mais barata do que parece
O Vitest implementa deliberadamente um expect compatível com Jest e utilitários de mock compatíveis com Jest. Isso torna o grosso de uma migração mecânico em vez de uma reescrita.
describe,it,test,beforeEache afins são iguais.- Os matchers de
expect(...)são compatíveis com Jest, então as asserções em geral não mudam. jest.fn()viravi.fn(),jest.spyOn()viravi.spyOn(),jest.mock()viravi.mock(). Na maior parte, localizar e substituir.- Snapshots são compatíveis em formato com Jest, então arquivos de snapshot existentes geralmente são aproveitados.
globals: truena configuração do Vitest mantémdescribe/it/expectambientes, então você não precisa adicionar imports em cada arquivo de teste no primeiro dia.
Velocidade, com honestidade
O Vitest costuma ser mais rápido, sobretudo em watch mode, onde reexecuta apenas o que mudou por meio de um grafo de dependências no estilo Vite. No CI em uma máquina fria a diferença é menor do que os benchmarks sugerem, porque ambos gastam boa parte do tempo de relógio em instalação e transform, não em asserções.
- O watch mode é onde a diferença é mais óbvia e onde as pessoas a sentem todo dia.
- Os dois suportam sharding entre máquinas de CI, então em grande escala convergem para o que o seu paralelismo permitir.
- Se você usa
ts-jest, parte da lentidão medida do Jest é verificação de tipos a cada execução, não o runner. Compare combabel-jestantes de atribuir isso ao Jest em si.
Quando permanecer no Jest
- Sua base é CommonJS e vai continuar assim. O principal argumento para migrar não se aplica.
- Você depende de um transformer customizado do Jest ou de um plugin específico dele sem equivalente no Vitest.
- Você está em React Native, onde o preset do Jest é o caminho trilhado.
- Sua suíte é grande, estável e ninguém reclama. Migração é um custo real contra um benefício medido em melhorias de experiência de desenvolvimento.
Conduzindo uma migração com segurança
- Inventarie transformers customizados, plugins do Jest e qualquer coisa que toque internals do Jest. Esta é a única parte ilimitada.
- Adicione o Vitest ao lado do Jest e configure
globals: truepara que os arquivos de teste existentes não precisem de edição. - Converta um diretório. Rode as duas suítes no CI e compare os resultados, não apenas a contagem de passa/falha.
- Faça localizar e substituir de
jest.porvi.nos arquivos convertidos, e conserte o punhado que não mapeia direto. - Regenere os snapshots deliberadamente e revise o diff em vez de aceitá-lo em bloco.
- Remova o Jest apenas quando o diff estiver vazio e a suíte inteira tiver rodado verde no novo runner por uma sprint completa.
The switching cost is mostly in the parts nobody lists
- Assertions and mocks usually port mechanically when the target implements a compatible API; custom transformers and framework plugins do not.
- Snapshot formats differ between runners, so plan to regenerate and review rather than port.
- Run both suites in parallel in CI for a period and diff the results. A migration that changes which tests fail is not a migration, it is a regression you have not found yet.
- Coverage numbers move on a runner change even when the tests do not, because instrumentation differs. Re-baseline any coverage gate deliberately.
The verdict
Projeto novo ou com muito ESM: Vitest. Suíte Jest grande já existente: fique no Jest a menos que a dor com ESM force a mudança.
Perguntas frequentes
Devo trocar Jest por Vitest?
--experimental-vm-modules do Node e substitui jest.mock() por jest.unstable_mockModule(). Se você está em CommonJS com uma suíte que funciona, não há razão forte para migrar.O Jest suporta ESM?
--experimental-vm-modules, definir transform: {} ou emitir ESM do seu transformer, e usar extensionsToTreatAsEsm para .ts e .jsx.Por que jest.mock() não funciona com ESM?
import estáticas antes de qualquer código seu rodar, então o hoisting do qual jest.mock() depende não pode acontecer. O Jest oferece jest.unstable_mockModule() para ESM, usado com um import() dinâmico do módulo sob teste.Quão difícil é migrar de Jest para Vitest?
expect compatível com Jest e mocks compatíveis com Jest. jest.fn() vira vi.fn() e assim por diante, snapshots são compatíveis em formato, e globals: true evita editar cada arquivo. A parte ilimitada são transformers e plugins customizados do Jest, então inventarie isso primeiro.O Vitest é de fato mais rápido que o Jest?
ts-jest, parte do que você atribui ao Jest é verificação de tipos a cada execução.O Vitest funciona sem Vite?
O Jest consegue verificar tipos nos meus testes TypeScript?
ts-jest. O caminho babel-jest transpila sem verificar tipos, o que a documentação do Jest afirma explicitamente, então erros de tipo em testes passam em silêncio. Isso pega de surpresa equipes que assumem que a suíte é verificada por tipos quando não é.