Angular 22 Testing Discipline
Write tests that verify user-visible behavior, route behavior, and service contracts. Keep test intent clear and avoid over-mocking the framework itself.
Core Rules
- Prefer behavior assertions over implementation details.
- Test components through their public inputs, outputs, and rendered DOM.
- Keep service tests focused on deterministic business logic and adapter behavior.
- Use route tests for navigation, redirects, and guard outcomes.
- Mock only external boundaries such as network, storage, or time.
- Match the test style to the risk: unit, integration, or browser-mode testing.
Angular Testing Setup
Angular CLI projects now default to Vitest and jsdom for unit tests. Use the default toolchain unless a project has a clear reason to customize it.
Component Test Pattern
import { Component } from '@angular/core';
import { TestBed } from '@angular/core/testing';
@Component({
standalone: true,
selector: 'app-counter',
template: `<button type="button" (click)="increment()">Count: {{ count }}</button>`,
})
class CounterComponent {
count = 3;
increment(): void {
this.count += 1;
}
}
describe('CounterComponent', () => {
it('renders and updates the current count', () => {
const fixture = TestBed.createComponent(CounterComponent);
fixture.detectChanges();
fixture.nativeElement.querySelector('button').click();
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Count: 4');
});
});
Service and Route Testing
import { describe, expect, it } from 'vitest';
describe('price service', () => {
it('applies discounts consistently', () => {
const applyDiscount = (price: number, percent: number) => price - price * percent;
expect(applyDiscount(100, 0.15)).toBe(85);
});
});
Testing Depth
- Use component tests for DOM, events, and rendering state.
- Use service tests for pure calculations and adapter behavior.
- Use route tests for redirects, guards, and navigation state.
- Use harnesses for complex Angular Material or CDK interactions when they reduce brittleness.
- Use browser mode only when a test depends on real browser behavior or visual fidelity.
Tooling Notes
- New Angular projects use Vitest and
jsdom by default.
- Use
providersFile or setupFiles for shared test infrastructure instead of repeating it in every spec.
- Keep test configuration close to the Angular build target so the behavior is obvious.
Example: Async Behavior
import { fakeAsync, tick } from '@angular/core/testing';
it('completes delayed work', fakeAsync(() => {
let completed = false;
setTimeout(() => {
completed = true;
}, 1000);
tick(1000);
expect(completed).toBe(true);
}));
Coverage and CI
- Collect coverage for the code paths that matter, not as a vanity metric.
- Keep CI runs deterministic and single-shot.
- Prefer setup files and providers files for shared test configuration instead of repeated boilerplate.
Anti-Patterns
- Do not assert private fields when the rendered output already proves the behavior.
- Do not overuse snapshots for interactive UI.
- Do not keep brittle test data inline when a tiny factory would read better.
Review Checklist
- The test fails for the right reason when behavior changes.
- The test body reads like a user scenario.
- Mocks only touch real external boundaries.
- Coverage gaps are intentional, not accidental.
1---2name: ng22-testing3description: Establishes Angular 22 testing habits for components, services, routing, and user interactions.4---5
6# Angular 22 Testing Discipline
7
8Write tests that verify user-visible behavior, route behavior, and service contracts. Keep test intent clear and avoid over-mocking the framework itself.
9
10## Core Rules
11
121. Prefer behavior assertions over implementation details.
132. Test components through their public inputs, outputs, and rendered DOM.
143. Keep service tests focused on deterministic business logic and adapter behavior.
154. Use route tests for navigation, redirects, and guard outcomes.
165. Mock only external boundaries such as network, storage, or time.
176. Match the test style to the risk: unit, integration, or browser-mode testing.
18
19## Angular Testing Setup
20
21Angular CLI projects now default to Vitest and `jsdom` for unit tests. Use the default toolchain unless a project has a clear reason to customize it.
22
23## Component Test Pattern
24
25```typescript
26import { Component } from '@angular/core';
27import { TestBed } from '@angular/core/testing';
28
29@Component({
30 standalone: true,
31 selector: 'app-counter',
32 template: `<button type="button" (click)="increment()">Count: {{ count }}</button>`,
33})
34class CounterComponent {
35 count = 3;
36
37 increment(): void {
38 this.count += 1;
39 }
40}
41
42describe('CounterComponent', () => {
43 it('renders and updates the current count', () => {
44 const fixture = TestBed.createComponent(CounterComponent);
45 fixture.detectChanges();
46
47 fixture.nativeElement.querySelector('button').click();
48 fixture.detectChanges();
49
50 expect(fixture.nativeElement.textContent).toContain('Count: 4');
51 });
52});
53```
54
55## Service and Route Testing
56
57```typescript
58import { describe, expect, it } from 'vitest';
59
60describe('price service', () => {
61 it('applies discounts consistently', () => {
62 const applyDiscount = (price: number, percent: number) => price - price * percent;
63
64 expect(applyDiscount(100, 0.15)).toBe(85);
65 });
66});
67```
68
69## Testing Depth
70
71- Use component tests for DOM, events, and rendering state.
72- Use service tests for pure calculations and adapter behavior.
73- Use route tests for redirects, guards, and navigation state.
74- Use harnesses for complex Angular Material or CDK interactions when they reduce brittleness.
75- Use browser mode only when a test depends on real browser behavior or visual fidelity.
76
77## Tooling Notes
78
79- New Angular projects use Vitest and `jsdom` by default.
80- Use `providersFile` or `setupFiles` for shared test infrastructure instead of repeating it in every spec.
81- Keep test configuration close to the Angular build target so the behavior is obvious.
82
83## Example: Async Behavior
84
85```typescript
86import { fakeAsync, tick } from '@angular/core/testing';
87
88it('completes delayed work', fakeAsync(() => {
89 let completed = false;
90
91 setTimeout(() => {
92 completed = true;
93 }, 1000);
94
95 tick(1000);
96
97 expect(completed).toBe(true);
98}));
99```
100
101## Coverage and CI
102
103- Collect coverage for the code paths that matter, not as a vanity metric.
104- Keep CI runs deterministic and single-shot.
105- Prefer setup files and providers files for shared test configuration instead of repeated boilerplate.
106
107## Anti-Patterns
108
109- Do not assert private fields when the rendered output already proves the behavior.
110- Do not overuse snapshots for interactive UI.
111- Do not keep brittle test data inline when a tiny factory would read better.
112
113## Review Checklist
114
115- The test fails for the right reason when behavior changes.
116- The test body reads like a user scenario.
117- Mocks only touch real external boundaries.
118- Coverage gaps are intentional, not accidental.