skip to content

TestBed Setup & Overrides

TestBed builds a fresh test environment: import the standalone component, swap providers with overrideProvider, resolve with TestBed.inject. Interviewers probe override order and teardown.

part ofAngularoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In Angular, how do you configure TestBed for a standalone component test, and what belongs in imports versus providers?

level: juniorimportance: must knowfreq 72%

answer

  1. a fresh dynamic module per test
  2. standalone goes where NgModules go
  3. doubles belong beside provideRouter
  4. TestBed.inject, never TestBed.get

basics

~10 s

Call TestBed.configureTestingModule with the standalone component and any modules it needs in imports and your service doubles in providers, then create it with TestBed.createComponent; a standalone component never goes in declarations.

solid answer

~30 s

`TestBed.configureTestingModule({ imports: [...], providers: [...] })` configures a fresh dynamic testing module for the current test. A standalone component belongs in `imports` — listing it in `declarations` throws — and because it carries its own template dependencies, importing it is optional: `TestBed.createComponent(MyComponent)` compiles it either way. `providers` holds environment-level services and doubles: `provideRouter([])`, `provideHttpClient()`, `{ provide: Api, useValue: fakeApi }`. You read a service back with `TestBed.inject(Api)` (`TestBed.get` was removed in v20). The first `createComponent` or `TestBed.inject` instantiates the module, after which further configuration throws, so all setup comes first.

code

ts · 10 lines
ts
TestBed.configureTestingModule({
  imports: [OrderSummary],
  providers: [
    provideRouter([]),
    {provide: OrderApi, useValue: {load: () => Promise.resolve({total: 42})}},
  ],
});

const api = TestBed.inject(OrderApi);
const fixture = TestBed.createComponent(OrderSummary);

go deeper

for a junior

Recall the shape: standalone component in imports, doubles in providers, createComponent to build it, TestBed.inject to read a service. Never declarations for standalone code.

for a middle

Explain that the testing module is rebuilt around every test, that createComponent or inject instantiates it and freezes configuration, and why importing a standalone component is optional.

for a senior

Show you keep setup lean: provide*() functions instead of legacy testing modules, only the doubles a spec needs, and a shared helper that still leaves each test in control of its providers.

for a principal

Frame the suite-wide conventions: which environment providers every spec gets by default, how legacy NgModule specs coexist during migration, and what a reviewer rejects in TestBed setup.

## What TestBed builds `TestBed` (from `@angular/core/testing`) is Angular's test-only way to stand up a slice of an application. Before each test it resets a **dynamic testing module** — an NgModule-shaped container Angular creates for you — and `TestBed.configureTestingModule()` fills it in. When the test first asks for something from it, TestBed compiles what is queued, creates the module's **environment injector**, and from then on hands out services and component fixtures from that injector. The metadata object you pass is a subset of `@NgModule` metadata plus a few test-only options: - `imports` — NgModules and **standalone** components, directives and pipes. - `providers` — providers for the testing module's injector. - `declarations` — only for **non-standalone** (NgModule-era) components, directives and pipes. - `schemas` — such as `NO_ERRORS_SCHEMA`, rarely a good idea. - Test-only options such as `teardown`, `errorOnUnknownElements`, `errorOnUnknownProperties`, `rethrowApplicationErrors` and `deferBlockBehavior`. ## imports, providers and declarations side by side | Array | What goes in it | Standalone-era example | |---|---|---| | `imports` | Standalone components/directives/pipes, NgModules | `imports: [OrderSummary]` | | `providers` | Services, tokens, `provide*()` functions, test doubles | `providers: [provideRouter([]), { provide: Api, useValue: fakeApi }]` | | `declarations` | Non-standalone declarables only | `declarations: [LegacyWidget]` (NgModule code) | Since v19, components, directives and pipes are **standalone by default**, so in new code `declarations` is almost always empty. Putting a standalone component there is an error: TestBed throws with a message saying the class is marked as standalone and cannot be declared, and asks whether you meant to add it to `imports`. Importing the component under test is **optional**. A standalone component carries its own template dependencies, so `TestBed.createComponent(OrderSummary)` works even with `configureTestingModule({})`. The CLI's generated spec still writes `imports: [OrderSummary]` and calls `compileComponents()`; both are harmless. ## A minimal standalone setup ```ts import {TestBed} from '@angular/core/testing'; import {provideRouter} from '@angular/router'; import {OrderSummary} from './order-summary'; import {OrderApi} from './order-api'; describe('OrderSummary', () => { const fakeApi = {load: () => Promise.resolve({total: 42})}; beforeEach(() => { TestBed.configureTestingModule({ imports: [OrderSummary], providers: [provideRouter([]), {provide: OrderApi, useValue: fakeApi}], }); }); it('renders the total', async () => { const fixture = TestBed.createComponent(OrderSummary); await fixture.whenStable(); expect(fixture.nativeElement.textContent).toContain('42'); }); }); ``` Three things make this work: 1. `OrderApi` is resolved from the testing module's injector, where the `useValue` provider shadows the real class (for example one declared `providedIn: 'root'`). 2. `provideRouter([])` gives the component the router services it injects without any real routes. 3. `createComponent` instantiates the module on first use and returns a `ComponentFixture`; driving that fixture is a separate subject. ## Getting services out: TestBed.inject `TestBed.inject(Token)` returns the instance the testing module's injector resolves for a token. It is typed (`TestBed.inject(OrderApi)` is an `OrderApi`) and accepts a fallback value and `inject()`-style options, so `TestBed.inject(Analytics, null)` returns `null` instead of throwing. Its predecessor `TestBed.get` was **removed in v20**. For code that must run in an injection context — a function that calls `inject()` itself — `TestBed.runInInjectionContext(() => ...)` runs it against the same environment injector. `TestBed.inject` reads the **testing module's** injector. It does not see providers a component declares in its own `providers` array; those live in the component's element injector. ## The rule that orders everything The first call that needs the module — `createComponent`, `TestBed.inject` or `runInInjectionContext` — **instantiates** it. After that, `configureTestingModule` and every `override*` method throw, telling you not to use `inject` before configuring. So the shape of every spec is: configure, override, then create and inject. ## NgModule-era specs Most production codebases still contain components declared in NgModules. Their specs look different, and interviewers expect you to read both: - A **non-standalone** component goes in `declarations`, together with any other non-standalone declarables its template uses, or you import the NgModule that declares and exports it. - Anything its template needs from outside — `CommonModule`, `FormsModule`, a shared UI module — goes in `imports`. - Migrating the component to standalone moves those template dependencies onto the component itself, which is why the standalone spec shrinks to a line or two. The provider side is the same in both styles: doubles go in `providers`, and `TestBed.inject` reads them back. ## Mistakes interviewers listen for - Putting a standalone component in `declarations` "because that is where components go". - Recreating the old `HttpClientModule` / `RouterTestingModule` imports instead of `provideHttpClient()` / `provideRouter([])` in `providers`. - Calling `TestBed.get`, which no longer exists. - Configuring once in a `beforeAll`: TestBed resets the testing module around every test, so setup belongs in `beforeEach`. - Expecting `TestBed.inject` to return an instance a component provides for itself.

  • Do you have to import a standalone component into the testing module before calling TestBed.createComponent on it?
    No. A standalone component carries its own template dependencies, so `TestBed.createComponent(MyComponent)` works with an empty `configureTestingModule({})`. Importing it is harmless and the CLI-generated spec does it by convention. Putting it in `declarations` instead is the mistake: TestBed throws and suggests `imports`.
  • How do you read an optional service in a spec without the test throwing when nothing provides it?
    Pass a fallback: `TestBed.inject(Analytics, null)` returns `null` when the token has no provider, or use the options form with `optional: true`. Without the fallback, a missing provider throws a `NullInjectorError` (NG0201).

saying these in an interview costs you the question

  • Standalone components are listed in the testing module's declarations array.
  • TestBed.get is still the way to read a service in a spec.
  • TestBed.inject returns the instance a component provides for itself.
  • Configuring TestBed once in beforeAll is enough for the whole file.
  • You must import HttpClientModule into the testing module to test HTTP code.
open as a page

In Angular, why can a configureTestingModule fake not replace a Clock a component provides itself, and how do you swap it?

level: middleimportance: must knowfreq 58%

basics

~20 s

A component's own providers create Clock in its element injector, which is asked before the testing module, so the module-level fake is never reached. Replace it with TestBed.overrideComponent on the providers metadata or TestBed.overrideProvider(Clock, { useValue }).

open as a page

In Angular's TestBed, what freezes the testing module, and in what order must configuration, overrides, compileComponents and inject run?

level: middleimportance: should knowfreq 45%

basics

~10 s

The first TestBed.inject, createComponent or runInInjectionContext instantiates the testing module, and later configure or override calls throw. So configure, then override, then await compileComponents if needed, then inject and create.

open as a page

An Angular suite passes spec by spec but fails in full runs, with services and DOM nodes surviving between tests; how does TestBed's teardown configuration bear on this?

level: seniorimportance: should knowfreq 36%

basics

~20 s

With teardown.destroyAfterEach true, the default, TestBed destroys fixtures and the testing module after each test, running service ngOnDestroy hooks and removing host elements. If it is false, or state lives outside DI, it leaks into the next test.

open as a page

Why does an Angular TestBed test fail on an error the running app would only log, and what does rethrowApplicationErrors control?

level: middleimportance: nice to knowfreq 27%

basics

~20 s

TestBed 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.

open as a page