Modern Angular
De Karma para Vitest no Angular: a config que realmente roda
Depois de anos de karma.conf.js, um test.ts de bootstrap e um launcher de browser, a config de Vitest do Angular v21 é quase nada: uma linha no angular.json. O ng new gera ela, o build target fornece o resto, e o jsdom roda os testes. Aqui está o que essa linha te dá, o que você adiciona, e o detalhe que explica por que o fakeAsync quebra.
O test target inteiro é uma linha
Depois do Karma você espera um arquivo de config. Não existe. O test target inteiro de Vitest no angular.json é a linha do builder: { "builder": "@angular/build:unit-test" }. Sem vitest.config.ts, sem karma.conf.js, sem test.ts de bootstrap pra manter.
O ng new no Angular v21 gera exatamente isso, mais um tsconfig.spec.json cuja única linha notável é "types": ["vitest/globals"]. É o que faz describe, it e expect funcionarem sem import. O runner usa jsdom por padrão. Esse é o setup inteiro.
Algumas docs mostram um target avançado carregando um providersFile. Isso é opcional, não o default. Comece pela linha única e adicione knobs só quando um teste precisar.
// O tsconfig.spec.json define "types": ["vitest/globals"], então describe, it
// e expect não precisam de import. O builder roda isso em jsdom por padrão.
describe('AppComponent', () => {
it('cria', () => {
const fixture = TestBed.createComponent(AppComponent);
expect(fixture.componentInstance).toBeTruthy();
});
});De onde vêm polyfills, styles e assets
A razão de o test target poder ser uma linha é que ele não redefine seu app. Ele reusa o build target do projeto (a configuração de development) para compilar o app sob teste. Então os polyfills, styles e assets que você define no build já estão valendo. Você não os repete no test.
Esse detalhe tem uma consequência que vale saber antes de escrever um único teste. Como os polyfills de build incluem zone.js, o zone.js é carregado nos testes também. Mas o runner do Vitest nunca envolve cada teste na zone de teste do Angular, então fakeAsync, tick e waitForAsync lançam o erro de ProxyZone. A config está certa. Os helpers de tempo é que precisam de reescrita, um trabalho separado.
| Preocupação | Onde se configura |
|---|---|
Test runner, jsdom, tsconfig.spec.json | O target test (@angular/build:unit-test) |
polyfills (zone.js), styles, assets | O target build (development), reusado automaticamente |
| Providers globais, coverage, browser real | Opções opt-in no test (abaixo) |
O install: adicionar Vitest, tirar Karma
Migrar um projeto Karma existente é, na maior parte, cirurgia de dependência. Adiciona Vitest e jsdom, remove os pacotes de Karma e Jasmine, e aponta o target test pro builder de unit-test. O Angular 22 traz um schematic migrate-karma-to-vitest que automatiza a troca; os passos abaixo são o que ele faz por baixo, e o que revisar quando termina.
A primeira rodada é ng test. No CI, adicione --no-watch, já que o watch mode liga por padrão num terminal e desliga fora dele. Rode os specs simples primeiro e confirme que passam antes de tocar em qualquer coisa que mexa com tempo.
npm install --save-dev vitest jsdom @vitest/coverage-v8
npm rm karma karma-chrome-launcher karma-jasmine karma-jasmine-html-reporter karma-coverage jasmine-core @types/jasmine
ng test --no-watchAs opções que valem adicionar
Mantenha o target mínimo e adicione só o que um teste de fato precisa. Essas são as que mais aparecem, todas em architect.test.options para você não redigitar a cada rodada.
| Necessidade | Opção em test.options |
|---|---|
Providers globais para todo teste (ex.: provideHttpClientTesting) | providersFile (o default export é um Provider[]) |
| Setup que roda depois dos polyfills e do TestBed | setupFiles |
| Coverage | coverage: true mais coverageReporters |
| Um browser real em vez de jsdom | browsers (precisa de @vitest/browser-playwright) |
| Sua própria config de Vitest para plugins | runnerConfig (o Angular carrega o arquivo mas não dá suporte ao conteúdo) |
A config é a parte fácil
Se o target é uma linha, por que a migração leva uma tarde? Porque a config nunca foi o trabalho. O trabalho são os specs que quebram quando a test zone some: os testes de fakeAsync e waitForAsync que precisam de uma reescrita para fake timers do Vitest. E antes de começar, vale decidir se a migração compensa para uma suíte específica.
A config te dá um runner verde em uma linha. O resto do trabalho é manter ele verde.
Artefato para reaproveitar
Primeira rodada Vitest verde, vindo do Karma
- Adicione
vitest,jsdome@vitest/coverage-v8como devDependencies. - Remova os pacotes de Karma e Jasmine (
karma*,jasmine-core,@types/jasmine). - Deixe o target
testcomo uma linha de builder:@angular/build:unit-test. - Confirme que o
tsconfig.spec.jsontemtypes: ["vitest/globals"], paradescribe/it/expectnão precisarem de import. - Rode
ng test --no-watche deixe os specs simples verdes antes de tocar nos testes defakeAsync. - Adicione
providersFile,coverageoubrowserssó quando um teste de fato precisar.
Perguntas frequentes
Preciso de um arquivo vitest.config.ts num projeto Angular?
Não, não para o setup padrão. O builder @angular/build:unit-test já traz defaults que funcionam (ambiente jsdom e tsconfig.spec.json). Você só adiciona uma config de Vitest pela opção runnerConfig quando precisa de plugins customizados, e o Angular carrega esse arquivo sem dar suporte ao conteúdo dele.
Onde coloco providers globais de teste como provideHttpClientTesting?
Num providersFile: um arquivo TypeScript cujo default export é um Provider[]. Aponte o test target para ele com a opção providersFile e ele vale para todo teste, o mesmo papel que o antigo test.ts fazia para setup global.
O runner Vitest do Angular usa um browser real?
Não, ele roda em jsdom por padrão. Adicione a opção browsers (que precisa de @vitest/browser-playwright) para rodar num browser headless real quando um teste precisar de layout real ou de uma API de browser que o jsdom não implementa.
Por que o zone.js ainda está carregado se eu migrei para Vitest?
Porque o build de teste reusa os polyfills do seu target build, e o zone.js está neles. É também por isso que o fakeAsync lança o erro de ProxyZone no Vitest: o zone.js está presente, mas o runner não estabelece a test zone que esses helpers precisam.
Como consigo coverage com o runner Vitest?
Defina coverage: true e coverageReporters (por exemplo text e lcov) nas opções de teste, ou passe --coverage. Ele usa o @vitest/coverage-v8, que você adiciona como devDependency.
Fontes consultadas
- https://angular.dev/guide/testing
- https://angular.dev/guide/testing/migrating-to-vitest
- https://angular.dev/reference/configs/angular-compiler-options
- https://vitest.dev/guide
- https://vitest.dev/config
- https://blog.angular.dev/announcing-angular-v21-57946c34f14b
Modern Angular Playbook
Este artigo é um play.
O Playbook do Angular Moderno reúne o diagnóstico, a matriz de adoção, onze jogadas e o plano de 30 dias. Grátis, em inglês e português.
Você recebe os dois PDFs por email, pela lista do Dojo IA.
André Ramosdisponível para vagas remotas, UTC−3Entre em contato →