Open-Source Wikis

/

Angular

/

How to contribute

/

Testing

angular/angular

Testing

Angular's tests run on Bazel. Different test types use different runners under the hood, but the entry point is always pnpm bazel test //… or one of the wrapper scripts in package.json.

Test surfaces

Test kind Runner Where it lives
Unit (browser) Karma + Jasmine packages/*/test/** and *.spec.ts
Unit (node) Jasmine via nodejs_test tools, compiler-cli, devtools backend
Template type-check tests Custom diff harness packages/compiler-cli/test/ngtsc/diagnostics
Public API goldens node goldens/public-api/manage.js test goldens/public-api/*.d.ts
Bundle-size goldens tools/symbol-extractor goldens/size-tracking/*.json
Integration pnpm bazel test //integration/... integration/<scenario>
DevTools E2E Cypress devtools/cypress
Performance Benchpress modules/benchmarks, modules/ssr-benchmarks
Tsec / trusted types bazelisk test with --build_tag_filters=tsec pnpm test-tsec

Daily commands

# Targeted run during iteration
pnpm bazel test //packages/core/test:test

# Watch mode for one target
pnpm bazel test //packages/core/test:test --test_arg=--watch

# Whole-repo run minus the slow suites
pnpm test:ci

pnpm test:ci excludes integration, adev, devtools, vscode language service, and SSR benchmarks. CI runs all of those separately.

The zoneless / Act-Wait-Assert pattern

AGENTS.md codifies the testing pattern for new tests in a zoneless world:

it('should reflect updated value', async () => {
  const fixture = TestBed.createComponent(MyComponent);

  // Act
  fixture.componentInstance.text.set('hello');

  // Wait
  await fixture.whenStable();

  // Assert
  expect(fixture.nativeElement.textContent).toContain('hello');
});

Rules:

  • Do not call fixture.detectChanges() to push updates. Signals schedule asynchronously; manual detectChanges() masks ordering bugs.
  • Use real async tests — it('...', async () => { ... }) — and await fixture.whenStable() between mutation and assertion.
  • For tests that depend on time, prefer useAutoTick() from packages/private/testing/src/utils.ts to fast-forward the mock clock.
  • Use await timeout(ms) from the same utils when you genuinely need to wait wall-clock time.

This pattern is enforced by review for any new test. Existing tests that still call detectChanges() are migrated opportunistically.

TestBed configuration

Standalone components mean most tests no longer need a per-test NgModule:

TestBed.configureTestingModule({
  imports: [MyStandaloneComponent],
  providers: [provideRouter([]), provideHttpClient()],
});

For environment-level providers (provideRouter, provideHttpClient, etc.), pass them via providers. The injector is environment-scoped automatically.

Browser vs node tests

  • Karma (browser) runs in the browser. Tests that touch the DOM, hydration, or platform-browser go here.
  • nodejs_test runs in pure Node. Tests for the compiler, language service, and any non-DOM logic go here.

The Bazel rule used by a given test target encodes the runner choice. Look at the BUILD.bazel next to the test source.

Public API and bundle size goldens

Public API surface is asserted via TypeScript declarations in goldens/public-api/. Adding a new export requires regenerating:

pnpm public-api:update

Bundle-size budgets are tracked in goldens/size-tracking/. Run:

pnpm symbol-extractor:update

after intentional bundle changes. CI fails if either snapshot differs from HEAD.

Integration tests

integration/ contains end-to-end scenarios that consume the built @angular/* packages, e.g.:

  • cli-hello-world, cli-hello-world-lazy — the smallest possible CLI consumer apps.
  • cli-signal-inputs — verifies signal input migration scenarios.
  • platform-server, platform-server-hydration — SSR with and without hydration.
  • defer@defer block runtime verification.
  • ng_elements@angular/elements integration.

Run:

pnpm integration-tests:ci

Each scenario has its own package.json. They install with pnpm against the locally built @angular/* artifacts.

DevTools E2E

Cypress runs against a development build of the DevTools panel:

pnpm devtools:devserver       # In one terminal
pnpm devtools:e2e:open        # Cypress UI
# or
pnpm devtools:test:e2e        # Headless

Test fixtures live in devtools/cypress/. The harness loads sample apps in an iframe and asserts the DevTools panel renders the expected component tree.

Tsec / trusted types

tsec is a TypeScript security linter that enforces Trusted Types contracts on the framework itself (the runtime is responsible for not introducing DOM-injection sinks). It runs via:

pnpm test-tsec

Exemptions live in packages/tsec-exemption.json.

Benchmarks

Run the local benchmark harness:

pnpm benchmarks

Apps under modules/benchmarks exercise component creation, change detection, hydration, and templated tables. The CI workflow benchmark-compare.yml runs the same suite against HEAD vs the merge base and posts a perf delta on PRs labeled requires: TGP.

Debugging a failing test

  • Add fdescribe / fit to focus a single test (Jasmine).
  • Insert a debugger; statement and run with the Bazel --config=debug flag plus a Node inspector for node tests.
  • For Karma, the --karma_debug flag runs the browser with DevTools open.

The full debugging walkthrough is in debugging and contributing-docs/debugging-tips.md.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Testing – Angular wiki | Factory