ADR-009-a — Frontend Testing Strategy (Angular 21)
ADR-009-a — Frontend Testing Strategy (Angular 21)
Sección titulada «ADR-009-a — Frontend Testing Strategy (Angular 21)»Draft
Context
Sección titulada «Context»[[adr-009-testing-strategy|ADR-009]] covers the project-wide testing philosophy; [[adr-009-b-backend-testing-strategy|ADR-009-b]] covers the backend. This document decides the frontend-specific rules for Angular 21 with Vitest.
Decision
Sección titulada «Decision»Three Test Layers. The anti-redundancy rule ([[adr-009-testing-strategy|ADR-009]] §3) applies across all layers.
| Layer | Owns | Example |
|---|---|---|
| Component logic | Signal graph, computed values, service HTTP contracts | occupancyColor(null) returns surface token |
| Template compliance | Design system rules via static string analysis on source files | No dark:, no *ngIf, uses inject() |
| Architectural guards | Codebase-wide structural invariants | features/ never imports shared/mocks/ data |
Signal Testing Rules. Test the signal graph, not the template rendering. Computed signals are pure functions — test them as such.
| Primitive | Rule |
|---|---|
signal() / computed() | Replicate the pure function, test with explicit inputs. No TestBed. |
linkedSignal() | Same as computed; or TestBed + flushEffects() if parent-dependent. |
effect() | TestBed + flushEffects(). Don’t assert execution count. |
httpResource() | HttpTestingController to flush, assert resulting signal values. |
Code examples in [[testing|docs/dev/testing.md]].
Component Test Rules.
| Scenario | Approach |
|---|---|
| Pure computed logic | Direct function replica — no TestBed |
| Service with HTTP | TestBed + provideHttpClientTesting() |
Component with input.required<>() | TestBed when DOM needed; otherwise replica |
| Guard or interceptor | TestBed with provideRouter stubs |
- Do not test PrimeNG internal rendering — test our data bindings and signal logic only.
- OnPush: always
fixture.detectChanges()explicitly after mutations. - No snapshot tests.
Design System Enforcement. Static string analysis on component .ts source files. Runs on every component.
| Forbidden | Reason |
|---|---|
dark: CSS class | PrimeNG tokens handle dark mode ([[adr-013-accessibility |
*ngIf / *ngFor | Use @if / @for |
Raw Tailwind colors (text-red-500) | Use PrimeNG tokens |
@Input() / @Output() | Use input() / output() signal API |
Missing OnPush | All components must declare it |
Architectural Guards.
| Guard | Enforces |
|---|---|
no-mock-imports.spec.ts | features/ has no runtime mock data imports ([[adr-022-mock-data-scope |
no-subscribe.spec.ts (proposed) | features/ has no .subscribe() — state lives in signals |
app.routes.spec.ts | Route paths in English, breadcrumbs in Spanish ([[adr-001-b-language-conventions |
Mock Data Discipline. Per [[adr-022-mock-data-scope|ADR-022]]: mocks allowed in shared/mocks/*.spec.ts and showcase/. Forbidden in features/ and core/ — use HttpTestingController. External image URLs forbidden.
Coverage Threshold. 80% (statements, branches, functions, lines) via v8 provider in vitest.config.ts. Drops below → CI fails.
What We Do Not Test. PrimeNG rendering, snapshots, E2E flows ([[adr-009-d-bdd-e2e-testing|ADR-009-d]]), accessibility automation (pending), Angular change detection.
Consequences
Sección titulada «Consequences»- (+) Three-layer model eliminates ambiguity about test placement.
- (+) Architectural guards are extensible — same pattern, no new infrastructure.
- (+) Template compliance runs on every component automatically.
- (-) Function replica pattern requires discipline to stay in sync with component logic.
- (-) Hour-related tests need
TZ=UTCorvi.setSystemTime()for reproducibility ([[adr-015-datetime-timezone|ADR-015]]).