Why does an Angular TestBed test fail on an error the running app would only log, and what does rethrowApplicationErrors control?
answer
- ErrorHandler logs, TestBed does more
- errors caught during change detection
- pending whenStable promises reject
- a last resort, per test
basics
~20 sTestBed reports errors Angular catches during change detection and event listeners to ErrorHandler and then rethrows them, so they fail the test instead of just logging. rethrowApplicationErrors, true by default, turns that rethrow off for one test.
solid answer
~40 sIn an app, errors Angular catches — during `ApplicationRef.tick`, or uncaught in template event listeners — go to `ErrorHandler`, which by default logs them and lets the app keep running. TestBed installs its own handler: it still calls your `ErrorHandler`, then rethrows, or, when `fixture.whenStable()` promises are pending, rejects them with the error. This landed in steps: tick errors in v19, listener errors in v20, and in v21 using `provideZoneChangeDetection()` stopped suppressing it. `configureTestingModule({ rethrowApplicationErrors: false })` restores log-only behaviour for that test; the changelog calls it a last resort. Prefer fixing the error, or asserting it: expect `TestBed.inject(ApplicationRef).tick()` to throw, or expect `whenStable()` to reject.
go deeper
Know that a TestBed test fails on errors the running app would only log, and that this is a feature: it catches templates and handlers that throw.
Explain the path: ErrorHandler first, then rethrow or reject pending whenStable promises, the default of true, and why the option is set per test.
Treat newly rethrown errors after an upgrade as real bugs, assert intended errors explicitly, and push back on blanket rethrowApplicationErrors: false changes.
Set the policy: which error options are strict suite-wide, and what justification a reviewer needs before any test opts out of rethrowing.
## What an application does with caught errors Angular catches some errors on your behalf rather than letting them escape to the browser: - errors thrown while change detection runs, inside `ApplicationRef.tick()` — a template expression that throws, a lifecycle hook that fails; - errors thrown by template event listeners, such as a `(click)` handler. It forwards them to the application's **`ErrorHandler`**. The default `ErrorHandler` logs to the console, and the application keeps running. In production that is often right: one broken widget should not take down the page. In a test it is dangerous. A component whose template throws on every check would render nothing, log an error, and — if nothing asserted on the missing text — pass. ## What TestBed does instead TestBed replaces the internal error path with its own handler: 1. It calls **your** `ErrorHandler` first (the one the testing module resolves, so a custom handler you provide still sees the error; if that handler itself throws, its error is used instead). 2. If any `fixture.whenStable()` promises are **pending**, it **rejects** them with the error. 3. Otherwise it **rethrows** the error, so it fails the test. The behaviour arrived in steps; the version matters when an upgrade suddenly turns green suites red: | Version | Change | |---|---| | v19 | Errors thrown during `ApplicationRef.tick` are rethrown in TestBed | | v20 | Uncaught errors in event listeners are also reported and rethrown | | v21 | Adding `provideZoneChangeDetection()` to TestBed providers no longer prevents the rethrow | ## The option `rethrowApplicationErrors` is a field of `TestModuleMetadata`, so it is set **per test** through `configureTestingModule`: ```ts TestBed.configureTestingModule({ imports: [StatusTicker], rethrowApplicationErrors: false, // last resort }); ``` - **Default: `true`.** - `false` means errors only go to `ErrorHandler`, which by default just logs them. - It is not one of the `initTestEnvironment` options, so it cannot be switched off suite-wide there; each test that needs it says so. The Angular changelog and error-handling guide describe `false` as a **last resort**, for the rare test that deliberately checks the application survives an error. ## Better options than turning it off **Fix the setup.** Most newly surfaced errors are real: a missing provider, an input the template assumes is set, a fake returning `undefined` where the template reads a property. The rethrow found a bug in the test or the component. **Assert the error when it is the point of the test.** Two patterns come straight from the changelog: - Trigger change detection synchronously and expect it to throw: `expect(() => TestBed.inject(ApplicationRef).tick()).toThrow()`. - Expect a pending stability promise to reject: with a pending `fixture.whenStable()`, TestBed rejects it with the error instead of throwing. **Test the handler, not the swallowing.** If the requirement is "errors are reported to our monitoring", provide a fake `ErrorHandler` and assert it was called. TestBed calls it before rethrowing, so the assertion still works — then catch or expect the rethrown error. ## A worked example A status ticker's template reads `status()!.label`, and its `status` signal starts as `undefined`. A test that wants to prove such failures reach monitoring, without switching the option off, can: 1. provide a recording `ErrorHandler` (an object whose `handleError` pushes into an array) in `configureTestingModule`; 2. create the component — in a zoneless test, the default since v21, a new fixture is attached for automatic change detection; 3. run change detection synchronously inside an assertion that expects a throw, `expect(() => TestBed.inject(ApplicationRef).tick()).toThrow()`, the pattern the v19 changelog gives; 4. assert that the recorder received the error. The recording handler sees the error first, TestBed then rethrows it out of `tick()`, and the test asserts both. ## How it relates to other TestBed error options - `errorOnUnknownElements` / `errorOnUnknownProperties` control whether template **compilation** problems — unknown elements and bindings — throw or only log. TestBed's own default is `false`; the CLI's Vitest setup initialises the environment with both set to `true`. - `teardown.rethrowErrors` controls errors thrown during **cleanup** after a test. - `rethrowApplicationErrors` controls errors caught while the app **runs** — change detection and listeners. Keeping all three strict is what stops a test suite from passing over broken components.
- A test provides a custom ErrorHandler that records errors. Does it still see an error TestBed rethrows?Yes. TestBed's handler calls the application's `ErrorHandler` first and only then rethrows, or rejects pending `whenStable()` promises. So you can assert the recorder captured the error, as long as the test also expects or catches the rethrown error.
- After an upgrade to v20, several tests fail with errors from (click) handlers. What changed?Since v20, uncaught errors in event listeners are reported to Angular's internal error handling as well as `ErrorHandler`, and TestBed rethrows them by default. The handlers were throwing before; the tests just could not see it. Fix the handlers or the test setup, or assert the error when it is intended.
saying these in an interview costs you the question
- TestBed only rethrows errors if you add a custom ErrorHandler.
- rethrowApplicationErrors defaults to false, matching production behaviour.
- Setting rethrowApplicationErrors: false is the normal fix after an upgrade.
- rethrowApplicationErrors can be switched off once in initTestEnvironment.
- When TestBed rethrows, your ErrorHandler is skipped entirely.