Voltar ao Blog
Angular Development20 de maio de 20259 min de leitura

Migrando de Jest para Vitest em um Projeto Angular

DR

Doug Ribeiro

9 min de leitura

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.

Pipeline do JestCódigo TypeScriptTransform com ts-jest / BabelSaída em CommonJSRunner do Jest (pool de workers…Mais de um minuto local, minuto…Cold start a cada execuçãoPipeline do VitestCódigo TypeScriptTransform com esbuild (nativo)Saída em ESM nativoWorkers paralelos por padrãoSegundos local, uma fração do t…HMR do Vite. Re-execução instan…VS

Passo 1: Trocar as Dependências

bash
# 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-angular

O 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

typescript
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.

typescript
// 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

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

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

AngularVitestTestingTypeScriptCI/CD