Vitest en Angular 22: por qué Karma ya no es el default
Son las 11 de la noche. Hay un commit pendiente de mergear y el pipeline de CI acaba de arrancar.
Primero levanta el contenedor. Después Chrome headless. Karma detecta los specs, los compila y — casi dos minutos después de tu push — arranca la primera suite.
Multiplica esos dos minutos por cada PR del día, por cada rebase, por ese "se me olvidó un punto y coma" que te obliga a repetir el ciclo entero.
No es una exageración. Es el ritual diario de cualquier equipo Angular con Karma en producción. Por eso Vitest en Angular 22 dejó de ser una curiosidad de nicho: es ya el camino que recomienda el propio equipo de Angular.
Por qué Karma se queda atrás
Karma no es lento porque esté mal hecho. Es lento porque hace algo que en 2026 ya no tiene sentido: lanzar un navegador real — Chrome, o el que hayas configurado — para ejecutar cada suite de tests.
Arrancar un navegador tiene un coste. Inicializar el motor de renderizado, cargar las extensiones de test, compilar el bundle con la configuración heredada de karma.conf.js… todo eso pasa antes de que se ejecute el primer expect().
Y luego está la ejecución. Karma corre las suites de forma secuencial por defecto. Si tienes 40 archivos de spec, esperas a que terminen uno detrás de otro.
Yo he trabajado en proyectos donde levantar el entorno de Karma tardaba varios minutos, antes de correr un solo test útil. Multiplica eso por cada push a un pipeline que corre veinte veces al día y tienes un cuello de botella silencioso que nadie cuestiona porque "siempre ha sido así".
Vitest cambia la premisa completa. En lugar de un navegador real, corre en un proceso de Node.js y simula el DOM con una librería de emulación — arranca en milisegundos, no en segundos. Y ejecuta los archivos de test en paralelo por defecto, no de forma secuencial.
No hace falta inventar un benchmark con un múltiplo llamativo para explicar esto. La diferencia cualitativa ya es suficiente: uno lanza un navegador, el otro no.
Vitest nativo en Angular 22: lo que es default y lo que no
Desde Angular 21, Vitest es el framework de testing por defecto para proyectos nuevos creados con ng new. Angular 22 mantiene ese default. Aquí hay que ser preciso, porque el estado real tiene matices que se pierden en los titulares.
Si generas un proyecto hoy con el CLI — tal y como lo hacemos desde cero en el curso de Angular Moderno —, Vitest ya viene configurado. No instalas nada, no tocas angular.json.
Karma, por otro lado, sigue soportado oficialmente. No ha sido eliminado ni deprecado. Sigue siendo una opción válida y documentada si tienes un proyecto existente y decides quedarte con él.
Lo que sí está marcado como experimental es otra cosa distinta: migrar un proyecto existente de Karma a Vitest. La documentación oficial de Angular lo dice sin rodeos: "Migrating an existing project to Vitest is considered experimental".
Esa distinción importa. Vitest de fábrica en un proyecto nuevo es el camino estándar y recomendado. El proceso de migración de un proyecto legacy con Karma es lo que todavía se etiqueta como experimental. No son lo mismo, y confundirlos te hace tomar decisiones equivocadas sobre cuándo migrar.
El builder detrás de todo esto se llama @angular/build:unit-test, y se configura en el target test de tu angular.json:
{
"projects": {
"mi-proyecto": {
"architect": {
"test": {
"builder": "@angular/build:unit-test"
}
}
}
}
}
Requiere el sistema de compilación application, que ya es el default en cualquier proyecto nuevo. Sus valores por defecto son "tsConfig": "tsconfig.spec.json" y "buildTarget": "::development" — no necesitas escribirlos a mano salvo que quieras cambiarlos.
¿Y el DOM? Vitest corre tus tests en un entorno Node.js, no en un navegador. Para simular document, window y el resto de la API del navegador usa una librería de emulación. El Angular CLI detecta automáticamente happy-dom si lo tienes instalado; si no, cae a jsdom como fallback.
Cómo migrar un proyecto existente
Si tu proyecto ya existe y corre sobre Karma, migrar no es instantáneo, pero tampoco es una reescritura. Son cinco pasos:
- Instala las dependencias:
npm install --save-dev vitest jsdom - Cambia el builder del target
testenangular.jsona@angular/build:unit-test - Revisa tu
karma.conf.jsen busca de configuraciones custom y trasládalas a unvitest.config.ts - Elimina
karma.conf.jsysrc/test.ts, y desinstala los paquetes de Karma (karma,karma-chrome-launcher,karma-coverage,karma-jasmine, etc.) - Opcional: si necesitas correr tests en un navegador real (modo browser), instala
@vitest/browser-playwrighty añade"browsers": ["chromium"]en la configuración
Ahora el gotcha que rompe configuraciones cuando nadie lo espera.
Con el builder viejo de Karma, podías meter tus opciones de build — polyfills, assets, estilos — directamente dentro del target test. Era cómodo, y casi nadie se paraba a pensar si estaba bien hecho.
El builder nuevo, @angular/build:unit-test, no soporta eso. Si las opciones de build que necesitas para tus tests son distintas de las de tu configuración normal de desarrollo, tienes que sacarlas de ahí y crear una configuración de build dedicada — normalmente un target development separado que el builder de test referencia.
Si tu proyecto tenía cualquier personalización de polyfills o assets dentro del target test, este es exactamente el punto donde la migración "automática" deja de serlo.
El schematic que automatiza parte del trabajo
Angular no te deja solo con los cinco pasos manuales. Existe un schematic que hace la parte mecánica de convertir sintaxis Jasmine a Vitest:
ng generate @schematics/angular:refactor-jasmine-vitest --project mi-proyecto --add-imports
Convierte automáticamente patrones como fit/fdescribe a it.only/describe.only, spyOn a vi.spyOn, jasmine.any a expect.any, y otras conversiones de sintaxis equivalentes.
Opciones útiles: --project <nombre> para apuntar a un proyecto específico del workspace, --include <path> para limitar el alcance, --add-imports para que añada los imports explícitos de Vitest que necesites, y --browser-mode si estás migrando hacia modo browser.
Ahora la parte honesta, porque prometerte una migración 100% automática sería mentirte.
El schematic no instala dependencias — eso lo haces tú a mano. No migra polyfills ni assets — ese es el gotcha del punto anterior, y sigue siendo tu responsabilidad. Y en escenarios de spies complejos — mocks anidados, spies sobre spies, configuraciones de retorno encadenadas — hace su mejor esfuerzo, pero necesitas revisar el resultado a mano.
Trátalo como un primer pase que te ahorra la mayor parte del trabajo mecánico, no como un botón de "migrar y olvidar".
Si además ya usas IA para generar o revisar tus tests — algo que cubrimos en testing en Angular con IA —, dale el resultado del schematic a tu agente y pídele que revise específicamente los spies antes de dar la migración por terminada.
Mapa de equivalencias: de Jasmine/Jest a Vitest
| Necesidad | Jasmine/Jest | Vitest |
|---|---|---|
| Función simulada | jest.fn() / jasmine.createSpy |
vi.fn() |
| Espiar método | jest.spyOn() |
vi.spyOn() |
| Mockear módulo | jest.mock() |
vi.mock() |
| Import real en mock parcial | jest.requireActual() |
vi.importActual() |
| Timers falsos | jest.useFakeTimers() |
vi.useFakeTimers() |
| Restaurar mocks | jest.clearAllMocks() |
vi.clearAllMocks() |
Matcher jasmine.any |
jasmine.any(Type) |
expect.any(Type) |
Fíjate en el patrón: casi todo lo que cambia empieza con jest. o jasmine. y pasa a vi.. Es el mocking y el motor de ejecución lo que cambia, no la forma de pensar tus tests.
Los matchers de aserciones — toBe, toEqual, toContain, toThrow, resolves, rejects — funcionan prácticamente igual en Vitest. Si ya sabes escribir un expect() en Jasmine o Jest, sabes escribir uno en Vitest. La curva de aprendizaje no está en las aserciones, está en el mocking.
Esto es justo lo que no cambia con el motor: los patrones de Testing Library (render, screen, userEvent) y la filosofía de testing por comportamiento en lugar de por implementación.
Eso es exactamente lo que cubrimos en el curso de Testing en Angular con Jest y Testing Library: sea cual sea el motor de tu proyecto — Jest hoy, Vitest mañana —, cómo piensas un test de comportamiento no cambia.
Testing zoneless con Vitest
Angular 22 empuja fuerte hacia zoneless. Y eso cambia también cómo escribes tus tests.
Con Zone.js, después de simular una interacción — un click, un input — a veces tenías que llamar fixture.detectChanges() manualmente para forzar que Angular actualizara la vista antes de tu expect().
En modo zoneless no hay Zone.js escuchando cada tarea async para disparar la detección de cambios. En su lugar, usas await fixture.whenStable() para esperar a que el ciclo de detección de cambios asíncrono termine:
it(39;actualiza el contador al hacer click39;, async () => {
const fixture = TestBed.createComponent(ContadorComponent);
fixture.nativeElement.querySelector(39;button39;).click();
await fixture.whenStable();
expect(fixture.nativeElement.textContent).toContain(39;139;);
});
Es un cambio pequeño en la sintaxis pero grande en la intención: pasas de forzar la detección de cambios a esperar a que el propio sistema te diga que está estable. Es la misma filosofía que estamos viendo en otras piezas de v22, como Signal Forms — otra API que va madurando y sobre la que conviene ser precisos respecto a qué está ya estable y qué sigue en evolución.
Karma vs Vitest en Angular 22, cara a cara
| Karma | Vitest | |
|---|---|---|
| Arranque de suite | Lanza un navegador real (Chrome u otro) | Corre en Node.js, simula el DOM con happy-dom o jsdom |
| Ejecución | Secuencial por defecto | Paralela por defecto |
| Configuración | karma.conf.js, heredada de webpack |
vitest.config.ts, integrada con el builder de Angular |
| Estado en Angular 22 | Soportado oficialmente, sigue siendo válido | Default para proyectos nuevos; migrar proyectos existentes es experimental |
La tesis
Cambiar de Karma a Vitest no es "un test runner más rápido". Es Angular alineando su tooling de testing con el ecosistema Vite y ESM que ya domina el resto del frontend — y quitándose de encima una dependencia que llevaba años siendo el cuello de botella silencioso de cualquier pipeline: un navegador real corriendo en CI.
Si estás empezando un proyecto hoy, no tienes nada que decidir — Vitest ya viene puesto. Si tienes un proyecto existente con Karma, tienes una decisión real que tomar, y ahora sabes exactamente qué parte de esa migración es estándar y cuál sigue siendo experimental.
Repasamos el resto de las novedades de v22 — de las que Vitest es solo una pieza — en el post de novedades de Angular v22. Y si quieres ver cómo aplicamos estos patrones en proyectos reales de producción, en Dominicode Labs es donde compartimos ese trabajo con la comunidad.
Preguntas frecuentes sobre Vitest en Angular 22
¿Vitest reemplaza completamente a Karma en Angular 22?
Reemplaza a Karma como default para proyectos nuevos, pero no lo elimina. Karma sigue soportado oficialmente y sigue siendo una opción documentada y válida si tienes un proyecto existente que prefieres no migrar todavía.
¿Necesito instalar plugins de terceros como Analog para usar Vitest en Angular 22?
No. El soporte de Vitest está integrado directamente en el Angular CLI a través del builder @angular/build:unit-test. No necesitas ningún plugin de terceros para el flujo estándar — solo instalar vitest y jsdom (o happy-dom) como dependencias de desarrollo.
¿Cómo migro mis tests de Jasmine a Vitest automáticamente?
Con el schematic ng generate @schematics/angular:refactor-jasmine-vitest, que convierte automáticamente la sintaxis de spies, matchers y bloques fit/fdescribe. No es una migración 100% automática: no instala dependencias, no migra polyfills ni assets, y los spies complejos necesitan revisión manual.
¿Qué le pasa a mis configuraciones de build al migrar de Karma a Vitest?
Si tu configuración de build para tests (polyfills, assets, estilos) era distinta de tu configuración normal de desarrollo, no puedes moverla dentro del target test como hacías con Karma. El nuevo builder no lo soporta — tienes que crear una configuración de build dedicada, por ejemplo un target development separado.
¿Vitest funciona con testing zoneless en Angular 22?
Sí, y de hecho es donde más se nota el cambio de paradigma: en lugar de llamar fixture.detectChanges() manualmente tras una interacción, usas await fixture.whenStable() para esperar el ciclo de detección de cambios asíncrono.
¿Debería migrar mi proyecto existente a Vitest hoy mismo?
Si tu suite de tests es grande y crítica para producción, pruébalo primero en una rama o en un proyecto secundario antes de tocar el repo principal — la documentación oficial etiqueta esta migración como experimental. Depende, en última instancia, de tu tolerancia al riesgo.
Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.
