In a new Angular CLI project, which builder and test runner does ng test use by default, and is Karma still supported?
answer
- the default changed in v21
- a unified unit-test builder
- a runner option with two values
- Node.js plus a simulated DOM
basics
~10 sSince v21, new projects run ng test through the @angular/build:unit-test builder with Vitest in Node.js and jsdom; Karma with Jasmine is still supported, selectable with --test-runner=karma or the builder's runner option.
solid answer
~40 sNew projects since v21 point the `test` target at the `@angular/build:unit-test` builder, whose `runner` option defaults to `"vitest"`, and `ng new` installs `vitest` and `jsdom`. Specs run in Node.js with a simulated DOM unless the `browsers` option names real browsers. The builder compiles specs with the application's build pipeline and sets up the TestBed environment itself, so there is no `test.ts`. Karma with Jasmine is still supported: `ng new --test-runner=karma`, or `"runner": "karma"` on the unit-test builder, and legacy Karma builders keep working. TestBed, fixtures and harnesses are the same under both runners; what differs is the spec vocabulary, such as spies and fake timers, which belongs to the runner.
code
json · 14 lines{
"projects": {
"profile-app": {
"architect": {
"test": {
"builder": "@angular/build:unit-test",
"options": {
"runner": "vitest"
}
}
}
}
}
}go deeper
Recall the current default: the unit-test builder with Vitest in Node.js and jsdom, and that Karma is still an option chosen with --test-runner=karma.
Explain the builder, runner and environment split, what the builder sets up for you, and which parts of a spec belong to Angular versus the runner.
Judge whether an existing Karma suite should stay or move, weighing speed, custom Karma plugins, Jasmine-specific spies and real-browser needs.
Plan runner policy across many projects: which default new projects get, when legacy suites migrate, and how to keep CI times and flakiness comparable during the transition.
## The short version Since **v21**, a project created with `ng new` runs its unit tests through the **`@angular/build:unit-test`** builder with **Vitest** as the runner, in a **Node.js process using a simulated DOM** (jsdom) rather than a launched browser. **Karma** with **Jasmine**, the default for many years, is **still supported** but is no longer what new projects get. ## What the pieces are - A **builder** is the Angular CLI plugin a target in `angular.json` points at; `ng test` runs the project's `test` target. The CLI's unit-test builder is `@angular/build:unit-test`. - A **runner** executes the compiled specs and reports results. The unit-test builder's **`runner`** option accepts `"vitest"` (the default) or `"karma"`. - A **test environment** is where the specs run: a DOM emulation inside Node.js, or a real browser selected with the builder's `browsers` option. The builder compiles the specs with the same build pipeline as the application (it defaults its `buildTarget` to the project's `build` target in the `development` configuration and its `tsConfig` to `tsconfig.spec.json`) and **initialises the Angular TestBed environment itself**, so projects no longer need a hand-written `test.ts` entry file. ## New project versus older project | | New project (v21 and later) | Typical older project | |---|---|---| | `test` target builder | `@angular/build:unit-test` | a Karma builder, such as `@angular-devkit/build-angular:karma` | | Runner | Vitest (`runner` defaults to `"vitest"`) | Karma launching a browser, specs written with Jasmine | | Where specs run | Node.js with jsdom by default | a real (often headless) Chrome | | Dev dependencies added | `vitest`, `jsdom` | `karma`, its launchers and reporters, `jasmine-core`, `@types/jasmine` | | Spec API | Vitest globals (`describe`, `it`, `expect`, `vi`) | Jasmine globals (`describe`, `it`, `expect`, `spyOn`) | ## Choosing Karma on purpose Karma remains a supported choice, for example for teams with large Jasmine suites or custom Karma plugins: 1. `ng new my-app --test-runner=karma` creates a project wired for Karma and Jasmine. 2. An existing project can keep the unit-test builder and set `"runner": "karma"` in the `test` target's options, adding the Karma and Jasmine packages and `"jasmine"` in `tsconfig.spec.json` types. 3. Older workspaces that still use a legacy Karma builder keep working; moving them to the unit-test builder with Vitest is an **optional** migration, not a forced one. ## What the builder handles for you The unit-test builder removes most of the hand-written wiring older projects carried: - **Test discovery**: `include` defaults to `**/*.spec.ts` and `**/*.test.ts`; `exclude` and a directory or file path narrow it. - **Environment set-up**: polyfills and the TestBed environment are initialised before any `setupFiles` run. - **Test-wide providers**: `providersFile` points at a file whose default export is an array of Angular providers. - **Watch mode**: `watch` defaults to `true` in an interactive terminal and `false` otherwise, so CI runs once. - **Coverage**: `ng test --coverage` enables it; include, exclude, reporters and thresholds are builder options. - **Selection**: `filter` takes a regular expression over suite and test names, and `listTests` prints the discovered files without running them. ## What does not change - **TestBed, ComponentFixture, HttpTestingController and CDK harnesses** are Angular APIs and work the same under either runner. - **The spec's own vocabulary** does change: spies, mocks and a few matchers are runner APIs, so Jasmine's `spyOn` and Vitest's `vi.spyOn` are not interchangeable. That is the part a migration has to rewrite. - **Zone-based helpers** such as `fakeAsync` rely on Zone.js being patched into the runner; with Vitest that means adding the `zone.js/plugins/vitest-patch` polyfill, while the recommended direction for zoneless projects is native `async` and the runner's fake timers. ## Common misconceptions - "Angular tests always run in Karma": true for years, not for new projects since v21. - "Karma is removed or deprecated in v22": it is still supported and still selectable. - "Vitest runs a real browser by default": it runs in Node.js with a simulated DOM unless `browsers` is configured. - "Switching runners changes how TestBed works": the Angular testing APIs are runner-independent.
- Why does a new project have no src/test.ts any more?The unit-test builder initialises the Angular TestBed environment itself before running the specs, together with the application's polyfills. Anything a project still needs globally goes into the builder's `setupFiles` option, and test-wide Angular providers can go into a `providersFile` whose default export is a provider array.
- Do TestBed-based specs have to change when the runner changes?The Angular parts do not: TestBed, ComponentFixture, HttpTestingController and CDK harnesses behave the same. The runner-specific parts do, such as Jasmine's `spyOn` and `jasmine.createSpy` versus Vitest's `vi.spyOn` and `vi.fn`, and zone-based `fakeAsync`, which needs the Zone.js Vitest patch or a move to the runner's fake timers.
saying these in an interview costs you the question
- Angular projects always run unit tests with Karma in Chrome
- Karma support was removed in Angular 22
- Vitest in the unit-test builder launches a real browser by default
- Changing the runner changes how TestBed and fixtures behave
- A new project still needs src/test.ts to initialise TestBed