VOOZH about

URL: https://javascript-conference.com/blog/angular-21-vitest-testing/

⇱ Vitest in Angular 21: Faster Testing


Angular Development

Vitest: The New Default Testing Solution in Angular

From Karma’s Legacy to Vite’s Velocity: How Angular 21 Redefines Test-Driven Development

Mar 10, 2026
Soumaya Erradi

10
Mar
With Angular 21, Vitest officially replaces Karma as the default testing framework, marking a major shift toward faster, modern, and Vite-powered testing. Let’s explore why the Angular team made the switch, how it improves the developer experience, and what you need to know to migrate and take full advantage of the new setup.

Introduction: The End of the Waiting Game

For over a decade, Angular has held a unique and sometimes contentious position in the frontend ecosystem. Unlike many competitors that treated testing as an optional add-on, Angular baked it into the core platform’s DNA. Since the release of Angular 2, Karma has been the faithful engine driving this philosophy. It provided stability in an era of browser fragmentation, ensuring that enterprise code ran correctly in “wild” environments like Internet Explorer 9, Chrome, and Firefox.

However, the landscape of web development has shifted dramatically. The rise of meta-frameworks, server-side rendering, and instant-feedback tooling has rendered the heavy, browser-based approach of Karma increasingly obsolete. Developers today do not just want stability; they demand speed. The friction of waiting 30+ seconds for a test suite to boot up has become a tax on productivity that modern teams are no longer willing to pay.

With the release of Angular 21, the framework officially adopts Vitest as the default unit testing solution. This is not merely a swap of libraries (like replacing Moment.js with date-fns); it is a fundamental architectural paradigm shift in how Angular applications are compiled, served, and validated. By leveraging the power of Vite’s unbundled development server, Angular 21 offers a testing experience that is orders of magnitude faster and significantly more capable than its predecessor.

In this comprehensive guide, we will dissect the architectural differences between Karma and Vitest, provide a robust migration strategy, explore how to test modern Angular features like Signals and Effects, and discuss how this shift radically optimizes CI/CD pipelines.

iJS Newsletter

Join the JavaScript community and keep up with the latest news!

Part 1: The Architecture of Slowness (and Why Karma Had to Go)

To truly appreciate the future, we must understand the mechanical limitations of the past. Karma was built for a different internet. In 2012, browser inconsistency was the primary enemy of the web developer. A test passing in Chrome might fail in Safari or IE8 due to non-standard DOM implementations. Therefore, Karma’s architecture was designed to spin up real browser instances and execute tests inside them.

The Karma Bottleneck

Karma operates on a complex client-server model that introduces latency at every step. When you run ng test in a legacy Angular project, the following sequence occurs:

  1. Webpack Compilation: Your entire application (or massive chunks of it) is bundled into JavaScript files. This is the critical bottleneck.
  2. Server Start: Karma starts a local web server.
  3. Browser Launch: It launches a real browser process (Chrome/Firefox) and “captures” it.
  4. Execution: The browser downloads the heavy bundle and executes the tests.
  5. Reporting: Results are serialized and sent back to the terminal via a socket connection.

The pain point is the bundling phase. Every time you save a single spec file, Webpack often recompiles the entire dependency graph. As applications grow, this feedback loop extends from milliseconds to seconds and eventually to minutes. In large enterprise monorepos, a simple “Cmd+S” can trigger a 45-second wait before the developer knows whether a test passed.

The Vitest Paradigm Shift

Vitest abandons the “real browser by default” approach in favor of speed and modern standards. It is built on top of Vite, a build tool that serves source code over native ES Modules (ESM).

When you run ng test in Angular 21:

  • Instant Server Start: Vitest starts a Node.js process almost instantly.
  • On-Demand Compilation: It does not bundle your app. It compiles only the specific files imported by your test file, on request.
  • Smart Invalidation: It relies on Vite’s module graph to ensure only tests affected by your changes are rerun.
  • Headless Execution: Tests run in a lightweight headless environment (JSDOM or happy-dom) that simulates browser APIs without the overhead of a graphical UI.

This architecture removes the overhead of browser startup and full-app bundling, resulting in a Hot Module Replacement (HMR) style feedback loop for testing. You save the file, and the test result appears instantly.

Feature Karma (Legacy) Vitest (Modern)
Execution Environment Real Browser (Chrome/Firefox) Node.js (via JSDOM/HappyDOM)
Compilation Strategy Full Bundle (Webpack) On-demand (Vite/ESBuild)
Watch Mode Speed Slow (re-bundles on change) Instant (HMR-like)
Parallelization Limited (requires sharding) Native Worker Threads
Debugging Browser DevTools VS Code / Vitest UI

Part 2: The Angular 21 Default Experience

In Angular 21, the CLI simplifies the testing setup significantly. When generating a new project (ng new my-app), the complex karma.conf.js is replaced by a sleek vitest.config.ts.

The Configuration File

Angular’s implementation of Vitest relies on a builder that abstracts away much of the boilerplate, but the configuration remains accessible and extensible.

// vitest.config.ts
import { defineConfig } from 'vitest/config';
import angular from '@analogjs/vite-plugin-angular'; // or official angular plugin

export default defineConfig({
 plugins: [angular()],
 test: {
 // Simulates a browser environment (window, document, etc.)
 environment: 'jsdom',
 // Allows using 'describe', 'it', and 'expect' globally without imports
 globals: true,
 // The setup file initializes the Angular testing environment
 setupFiles: ['./src/test-setup.ts'],
 // Only include spec files to avoid confusion with source files
 include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
 // Threading settings for performance
 pool: 'threads',
 poolOptions: {
 threads: {
 singleThread: false, // Set to true for debugging complex issues
 }
 },
 // Clean up mocks automatically to prevent leaks across test files
 restoreMocks: true,
 reporters: ['default', 'html'], // 'html' generates a visual report
 },
});

The Test Setup

The connection between Angular’s Dependency Injection system and Vitest happens in test-setup.ts. This file replaces test.ts from the Karma era.

// src/test-setup.ts
import '@angular/localize/init'; // Required for i18n support
import 'zone.js/testing'; // Essential for Angular's async detection
import { getTestBed } from '@angular/core/testing';
import {
 BrowserDynamicTestingModule,
 platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';

// Initialize the Angular testing environment
getTestBed().initTestEnvironment(
 BrowserDynamicTestingModule,
 platformBrowserDynamicTesting()
);

Key Insight: Even though Vitest runs in Node, we still import zone.js/testing. This is crucial because Angular’s change detection relies on Zones. This ensures utilities like fakeAsynctick, and flush continue to work exactly as they did in Karma, easing the transition.

iJS Newsletter

Join the JavaScript community and keep up with the latest news!

Part 3: Migration Guide – From Karma to Vitest

Migrating an existing application is less daunting than it appears. The syntax for writing tests in Angular has always been abstracted by the TestBed API, which remains unchanged. The migration is primarily infrastructural.

Step 1: Clean House

First, remove the legacy dependencies. This reduces node_modules bloat and prevents configuration conflicts.

npm uninstall karma karma-chrome-launcher karma-coverage karma-jasmine \
karma-jasmine-html-reporter jasmine-core @types/jasmine

Step 2: Install Vitest Ecosystem

You will need Vitest, the UI library (highly recommended for debugging), and the JSDOM environment.

npm install –save-dev vitest @vitest/ui jsdom @analogjs/vite-plugin-angular

Step 3: Global Types and Compilation

One of the most common friction points is the clash between Jasmine types (which Karma used) and Vitest types (Jest-compatible). You need to explicitly tell TypeScript which types to load.

Update your tsconfig.spec.json:

{
 "extends": "./tsconfig.json",
 "compilerOptions": {
 "outDir": "./out-tsc/spec",
 "types": [
 "vitest/globals",
 "node" // Required for JSDOM access
 ]
 },
 "include": [
 "src/**/*.spec.ts",
 "src/**/*.d.ts"
 ]
}

Step 4: Refactoring Spies and Mocks

This is the only area where code changes are frequent. While Vitest supports Jasmine-style syntax in many cases, its native spying utility (vi) is more powerful.

The “Spy” Shift:

  • Karma/Jasmine: spyOn(obj, ‘method’).and.returnValue(…)
  • Vitest: vi.spyOn(obj, ‘method’).mockReturnValue(…)

Example Migration:

// LEGACY (Jasmine)
spyOn(authService, 'login').and.returnValue(of(true));
expect(authService.login).toHaveBeenCalledWith('user', 'pass');

// MODERN (Vitest)
import { vi } from 'vitest';

const loginSpy = vi.spyOn(authService, 'login');
loginSpy.mockReturnValue(of(true));
expect(authService.login).toHaveBeenCalledWith('user', 'pass');

Part 4: Advanced Capabilities & Modern Testing Strategies

Moving to Vitest isn’t just about parity; it’s about gaining capabilities that were difficult or painful with Karma.

1. Snapshot Testing

Snapshot testing captures rendered DOM output and stores it in a file. If the HTML structure changes unexpectedly, the test fails. This is invaluable for dumb/presentational components to prevent UI regressions.

it('should render the dashboard layout correctly', () => {
 const fixture = TestBed.createComponent(DashboardComponent);
 fixture.detectChanges();
 // Serializes the HTML and compares it to the stored snapshot
 expect(fixture.nativeElement).toMatchSnapshot();
});

2. Testing Signals and Effects

Angular 21 relies heavily on Signals. Because Signals are synchronous by nature, tests often become simpler. However, testing Effects requires a trick because they run asynchronously during the change detection cycle.

it('should update the computed signal when input changes', () => {
 const component = fixture.componentInstance;
 // Set signal directly
 component.quantity.set(5);
 
 // Trigger change detection to update computed signals
 fixture.detectChanges(); 
 
 expect(component.totalPrice()).toBe(50);
});

it('should trigger an effect', async () => {
 const logSpy = vi.spyOn(console, 'log');
 const component = fixture.componentInstance;
 
 component.userId.set('123');
 fixture.detectChanges();
 
 // Effects run asynchronously; wait for them to settle
 await fixture.whenStable();
 
 expect(logSpy).toHaveBeenCalledWith('User ID changed to 123');
});

3. Modern HTTP Testing

Testing Services that make HTTP calls are a staple of Angular apps. With Vitest, you combine HttpTestingController with standard expectations.

import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';

describe('UserService', () => {
 let service: UserService;
 let httpMock: HttpTestingController;

 beforeEach(() => {
 TestBed.configureTestingModule({
 providers: [
 UserService,
 provideHttpClient(),
 provideHttpClientTesting(),
 ]
 });
 service = TestBed.inject(UserService);
 httpMock = TestBed.inject(HttpTestingController);
 });

 it('should fetch user data', () => {
 service.getUser('1').subscribe(user => {
 expect(user.name).toBe('Alice');
 });

 const req = httpMock.expectOne('/api/user/1');
 expect(req.request.method).toBe('GET');
 req.flush({ name: 'Alice' });
 });
});

4. In-Source Testing

Vitest allows tests to live inside source files. This is perfect for pure utility functions where creating a dedicated .spec.ts file adds file-tree clutter.

// src/app/utils/math.ts
export function calculateTax(amount: number): number {
 return amount * 0.2;
}

// This block is stripped out during production builds
if (import.meta.vitest) {
 const { it, expect } = import.meta.vitest;
 it('calculates tax correctly', () => {
 expect(calculateTax(100)).toBe(20);
 });
}

iJS Newsletter

Join the JavaScript community and keep up with the latest news!

Part 5: Developer Experience (DX) and Tooling

The biggest upgrade in Angular 21 isn’t just raw speed; it’s the Developer Experience.

Vitest UI

Vitest comes with an optional UI that visualizes your test suite. Run: npx vitest –ui

This launches a web dashboard where you can:

  • View the module graph to see which files are testing what.
  • See a real-time log of console output for specific tests.
  • Visually debug: Click on a test to see the code and error stack trace side-by-side.

VS Code Integration

The Vitest VS Code extension is a game-changer. It puts “Run” and “Debug” buttons directly next to your it blocks in the editor.

  • Debugging: You can set a breakpoint in your TypeScript code, right-click the test icon in the gutter, and select “Debug Test.” Because Vitest runs in Node, the debugger attaches instantly, with no more complex Chrome remote debugging setups.

Part 6: CI/CD Integration and Performance

One of Karma’s hidden costs was CI execution time. Running headless Chrome in Docker containers consumes significant memory and CPU, often leading to flaky timeouts.

Vitest improves CI in three major ways:

  1. V8 Coverage: Vitest uses native V8 code coverage (the engine inside Node/Chrome) rather than instrumenting code with Babel/Istanbul. This makes generating coverage reports nearly free in terms of performance.

npx vitest run –coverage

  1. Concurrency: Vitest runs test files in parallel using worker threads by default. If you have a 16-core CI runner, Vitest will utilize all cores to crunch through tests.
  2. No Browser Dependencies: You no longer need to install Chrome or configure puppeteer in your Docker images. A standard Node.js container is all you need.

Performance Benchmark (Real-World Example):

  • Project: Medium-sized Monorepo (~5,000 tests)
  • Karma Execution: ~4 minutes (plus flaky browser disconnects)
  • Vitest Execution: ~45 seconds

Even when exact numbers vary, the direction is consistent: faster pipelines, shorter feedback cycles, and higher confidence in merges.

Part 7: The “Gotchas”: JSDOM vs. Real Browser

The biggest mental shift for Angular developers is accepting that JSDOM is not a full browser. It is a JavaScript implementation of browser APIs running inside Node.js.

Limitations

  • Rendering: JSDOM does not paint pixels. getBoundingClientRect()offsetWidth, and innerHeight will often return 0 or defaults.
  • Missing APIs: Features like ResizeObserverIntersectionObserverCanvas, and WebGL are not present by default.

The Solution: Mocking

If your component relies on these APIs, you must mock them in test-setup.ts.

// Mocking matchMedia
Object.defineProperty(window, 'matchMedia', {
 writable: true,
 value: (query: string) => ({
 matches: false,
 media: query,
 onchange: null,
 addListener: () => {}, // Deprecated
 removeListener: () => {}, // Deprecated
 addEventListener: () => {},
 removeEventListener: () => {},
 dispatchEvent: () => {},
 }),
});

// Mocking ResizeObserver
global.ResizeObserver = class ResizeObserver {
 observe() {}
 unobserve() {}
 disconnect() {}
};

Strategic Advice: If a test strictly requires layout calculation (e.g., “does this dropdown fit on the screen?”), that test belongs in End-to-End (E2E) testing with Playwright or Cypress, not in unit tests. Vitest covers logic; Playwright covers rendering.

iJS Newsletter

Join the JavaScript community and keep up with the latest news!

Conclusion: Embracing the Future

The transition from Karma to Vitest in Angular 21 is a clear statement: Angular is committed to modern tooling and fast feedback loops. By adopting ecosystem-standard tools, Angular becomes more approachable for developers coming from React or Vue and significantly more enjoyable for long-time Angular teams maintaining large codebases.

The benefits are immediate. Speed keeps developers in the “flow state.” Debugging improves thanks to clearer error reporting and modern UI tools. Configuration shrinks, and the entire toolchain aligns better with modern web standards.

Migration requires attention to detail (specifically regarding test types and browser API mocking), but the payoff is a testing suite that feels like an asset rather than a burden. Vitest positions Angular applications to be faster, leaner, and ready for whatever the next generation of web development brings.

Soumaya Erradi

I'm an experienced web developer and a passionate IT and electronics trainer, specializing in frontend development and enterprise applications built with Angular. I spend most of my time exploring what's new in the tech world and helping other developers improve their skills. As a conference speaker, I bring advanced Angular topics, tips for the integration of smart contracts and best solutions for web3 applications.

🔍 Frequently Asked Questions (FAQ)

1. What is Vitest in Angular 21?

Vitest is the new default unit testing framework introduced in Angular 21. It replaces Karma and runs tests in a Node.js environment using Vite’s modern tooling. This change significantly improves test performance and developer experience compared to browser-based testing setups.

2. Why did Angular replace Karma with Vitest?

Angular replaced Karma because its browser-based architecture requires full bundling and browser startup, which slows down test execution. Vitest uses Vite’s on-demand compilation and runs tests in a lightweight environment like JSDOM, resulting in dramatically faster feedback cycles.

3. How does Vitest improve Angular testing performance?

Vitest improves performance by compiling only the files required for each test instead of bundling the entire application. It also runs tests using worker threads and avoids launching full browser instances, making test execution significantly faster.

4. How do you migrate from Karma to Vitest in Angular?

Migrating typically involves removing Karma and Jasmine dependencies, installing Vitest and its ecosystem packages, updating TypeScript configuration to include Vitest types, and replacing Jasmine spies with Vitest’s vi.spyOn() utilities. The Angular TestBed API remains unchanged, which simplifies the transition.

5. Does Angular still use TestBed with Vitest?

Yes. Angular’s TestBed API remains the core testing utility even when using Vitest. The main difference is that Vitest executes the tests instead of Karma while Angular’s testing infrastructure continues to handle dependency injection and component testing.

6. Can Vitest run Angular tests without a browser?

Yes. Vitest runs tests in Node.js using environments such as JSDOM or happy-dom. These environments simulate browser APIs, allowing Angular components and services to be tested without launching a real browser.

7. How does Vitest improve CI/CD pipelines for Angular projects?

Vitest speeds up CI pipelines by running tests in parallel worker threads and using native V8 code coverage instead of slower instrumentation tools. Because it does not require a browser environment, CI containers also become simpler and more reliable.

8. Are there limitations when testing Angular with Vitest?

Because Vitest commonly uses JSDOM instead of a real browser, some layout-related APIs like getBoundingClientRect() or ResizeObserver may require mocking. Tests that rely on real rendering behavior are typically better suited for end-to-end testing tools like Playwright or Cypress.

Download “Building AI-Powered Frontends Whitepaper”

👁 Image

👁 Image

Top Articles About Angular Development

Angular Development
Jun 4, 2026
👁 Angular 22: What’s New in the Latest R...

Angular 22: What’s New in the Latest R...

In this version, the Angular team has prioritized comprehensive optimization: from the new default O...

Angular Development
May 13, 2026
👁 Watch Session: An AI Assistant for Your ...

Watch Session: An AI Assistant for Your ...

AI assistants are becoming an important part of modern web applications. They can help users complet...

Angular Development
Apr 23, 2026
👁 Tool Calling in the Frontend with Hashbr...

Tool Calling in the Frontend with Hashbr...

Hashbrown streamlines the complexity of integrating AI assistants into web apps for Angular and othe...

Enjoying the content?

Get the most out of International JavaScript Conference by becoming a free community member — curated resources, weekly newsletter, and member-only perks.

Weekly
Articles + tutorials

The reads you'd find if you had time

2× / mo
Live webinars

Experts you can actually ask

Monthly
Magazine + whitepapers

Deep dives worth your weekend

On-demand
Recordings + courses

Past conferences, ready when you are

Angular Development

Master Angular Development for Scalable Web Applications.

JavaScript Testing & Security

Enhancing Security and Code Quality in JavaScript.

Node.js & Backend

Node.js for Scalable Backend Solutions.

React.js & Next.js Development

Master React & Next.js for Dynamic Web Apps.

DevOps CI/CD

Streamline JavaScript development with DevOps practices.

Web Architecture & Performance

Master Web Architecture and Performance Optimization in JavaScript.

Generative AI in JavaScript

Push Your JavaScript Apps to the Next Level with GenAI and Cutting-Edge Web Innovation.

Free Membership

Stay ahead in JavaScript – without the doomscrolling.

Curated articles, deep dives, and live experts. Delivered, not hunted.

Email
No spam · Unsubscribe anytime
Plus: Conference & Camp early birds and discounts
Free Membership

Stay ahead in JavaScript – without the doomscrolling.

Curated articles, deep dives, and live experts. Delivered, not hunted.

Email
No spam · Unsubscribe anytime
Plus: Conference & Camp early birds and discounts