Actualización, septiembre de 2026: lee esto primero si estás en un Angular reciente. Angular 21 incluye @angular/build:unit-test con Vitest como runner por defecto, y el equipo de Angular publica una guía de migración en https://angular.dev/guide/testing/migrating-to-vitest. En Angular 20 o 21, usa ese builder en lugar del setup manual de abajo: no necesitas el plugin de Analog, ni un vitest.config.ts, ni tu propio archivo test-setup. Todo lo que va del Paso 1 al Paso 5 aquí es la receta para Angular 19 y anteriores, escrita en mayo de 2025. Las notas sobre la API de Jest a Vitest más abajo aplican en cualquier caso.
Jest me estaba costando tiempo real. En MailHive, el producto SaaS que construyo solo, la suite completa del front end Angular tomaba bastante más de un minuto localmente y varios minutos en GitLab CI. Cada merge request tenía un impuesto de varios minutos solo para ver verde. Cambié a Vitest y reduje ambos a una fracción. Esto es exactamente lo que hice.
Por Qué Jest Se Vuelve Lento
Jest fue diseñado para CommonJS y depende de Babel o ts-jest para transpilar TypeScript antes de ejecutar cualquier cosa. Esa transformación ocurre en cada ejecución en frío. Vitest está construido sobre Vite, que usa esbuild para transformaciones y ESM nativo. También ejecuta archivos spec en paralelo por defecto (procesos hijos desde Vitest 2, worker threads si lo habilitas) y mantiene el grafo de módulos de Vite para ejecutar solo los tests afectados por tus cambios en modo watch. La diferencia es estructural, no cosmética.
Paso 1: Intercambiar las Dependencias
# 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-angularEl paquete @analogjs/vite-plugin-angular maneja la compilación de Angular dentro de Vite. Sin él, el compilador de templates y los decoradores de Angular no funcionarán en el entorno de Vitest.
Paso 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 es importante. Hace que describe, it, expect, vi estén disponibles sin imports, coincidiendo con la API de Jest que tus tests existentes esperan.
Paso 3: Reemplazar el Archivo de Setup de Tests
Elimina setup-jest.ts o src/test.ts. Reemplázalo con un src/test-setup.ts nuevo que inicialice el TestBed de 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()
);Una nota de versión sobre ese archivo: @angular/platform-browser-dynamic/testing era el import correcto en mayo de 2025 y quedó reemplazado a partir de Angular 20. En Angular 20 en adelante, toma BrowserTestingModule y platformBrowserTesting desde @angular/platform-browser/testing en su lugar. La llamada a initTestEnvironment es idéntica por lo demás.
Paso 4: Actualizar tsconfig.spec.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": ["vitest/globals"]
},
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}Elimina @types/jest de types si todavía está ahí. Tener ambos causa conflictos de tipos.
Paso 5: Actualizar los Scripts de package.json
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
}
}El Impuesto de Migración: API de Jest a Vitest
El trabajo real de la migración es un buscar-y-reemplazar global en tus archivos spec. Las APIs son casi idénticas por diseño.
- jest.fn() se convierte en vi.fn(). Haz un reemplazo global de 'jest.' a 'vi.'
- jest.mock() se convierte en vi.mock(). El comportamiento de hoisting es ligeramente diferente, consulta la documentación de Vitest sobre hoisting si usas funciones factory
- Los snapshots se conservan en su mayoría. Vitest escribe un encabezado de archivo distinto y formatea algunos casos de otra manera (snapshots de error, separadores de mensaje personalizados), así que espera algunos diffs. Regenerarlos una vez después de la migración es la solución más simple
- Los matchers de @testing-library/jest-dom necesitan el import de vitest: import '@testing-library/jest-dom/vitest'
- Zone.js todavía necesita ser importado en tu archivo setup. El TestBed de Angular lo espera
- fakeAsync, tick y TestBed son de Angular, no de Jest. Funcionan sin cambios
Cómo Se Vio en un Proyecto Real
El proyecto era el front end Angular de MailHive: mayormente tests de servicios con una buena parte de tests de componentes usando TestBed, todo bloqueando merges en GitLab CI detrás de un umbral de cobertura.
- Jest con ts-jest (frío): bastante más de un minuto localmente
- Vitest (frío): cuestión de segundos
- Vitest (cache caliente): re-ejecuciones casi instantáneas en modo watch
- GitLab CI shared runner, Jest: varios minutos por pipeline
- GitLab CI shared runner, Vitest: una fracción de eso, lo suficientemente rápido para que el job de tests dejara de ser el cuello de botella
Esa mejora en CI no es solo un número. Significa que el feedback llega antes de que cambies de contexto. Los merge requests se mueven más rápido. Incluso trabajando solo, dejas de temer al job de tests.
Un Gotcha Que Vale la Pena Mencionar
Si algún test todavía espía Date o Math.random con jest.spyOn, por ejemplo en un helper de tests compartido que no es un archivo spec y se escapó del buscar y reemplazar, va a fallar porque no existe el global jest en Vitest. Reemplaza con vi.spyOn. Además, vi.useFakeTimers() funciona igual que jest.useFakeTimers() pero necesitas llamar vi.useRealTimers() en afterEach si no lo estás haciendo ya. Tuve algunos tests que empezaron a fallar intermitentemente hasta que rastreé este problema.
Los tests rápidos cambian el comportamiento. Cuando el ciclo de feedback es instantáneo, los desarrolladores ejecutan tests antes de hacer commit. Cuando toma minutos, hacen push y esperan lo mejor.
Presupuesta una tarde para un proyecto Angular típico. La mayor parte es el buscar-y-reemplazar de la API y perseguir un puñado de edge cases. La ganancia se compone en cada ejecución de tests de ahí en adelante.
Referencias
- Vitest documentation. https://vitest.dev/guide/
- Analog vite-plugin-angular. https://analogjs.org/docs/packages/vite-plugin-angular