skip to content

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%

answer

  1. the first instantiation closes the door
  2. nested beforeEach hooks run outer first
  3. compileComponents does not instantiate
  4. defer blocks need the async step

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.

solid answer

~40 s

TestBed collects configuration lazily and builds the testing module on first use: `createComponent`, `TestBed.inject` and `runInInjectionContext` all instantiate it. After that, `configureTestingModule` and every `override*` method throw an error saying the module is already instantiated and to avoid `inject` before the call. Among themselves, configure and override calls can come in any order and can repeat; providers accumulate, and the last provider for a token wins. `compileComponents()` does not instantiate the module; the current guide says it is only required when tested components use `@defer` blocks, so await it after all overrides. The classic failure is an outer `beforeEach` that injects a service, followed by a nested `beforeEach` that overrides one.

code

ts · 13 lines
ts
function setup(opts: {clock?: Partial<Clock>} = {}) {
  TestBed.configureTestingModule({imports: [ShiftBanner]});
  if (opts.clock) {
    TestBed.overrideProvider(Clock, {useValue: opts.clock});
  }
  return TestBed.createComponent(ShiftBanner);
}

it('greets the morning shift', async () => {
  const fixture = setup({clock: {now: () => new Date(2026, 0, 5, 9, 0)}});
  await fixture.whenStable();
  expect(fixture.nativeElement.textContent).toContain('Good morning');
});

go deeper

for a junior

Remember the order: configure, override, then create or inject. Once a fixture or service exists, TestBed will not accept more configuration.

for a middle

Explain which calls instantiate the module, why nested beforeEach hooks trip the freeze, how repeated configuration accumulates, and what compileComponents is still for.

for a senior

Design suites so outer hooks only configure, prefer a parameterised setup function, and diagnose the already-instantiated error from the hook order rather than by trial and error.

for a principal

Set a house pattern for spec setup that scales across teams: a setup function per component with explicit options, and a rule about where instantiating calls may appear.

## Lazy configuration, one-way instantiation TestBed works in two phases for every test: 1. **Configuration.** `configureTestingModule`, `overrideComponent`, `overrideDirective`, `overridePipe`, `overrideModule`, `overrideProvider` and `overrideTemplate` only record what you asked for. Nothing is built yet. 2. **Instantiation.** The first call that needs a real injector makes TestBed finalize: compile anything still queued, create the dynamic testing module, apply provider overrides, and create the environment injector. Instantiation is one-way for the rest of the test. Every configure and override method checks whether the module already exists and throws if it does, with a message of the form "Cannot override provider when the test module has already been instantiated. Make sure you are not using `inject` before `overrideProvider`." ## Which calls instantiate | Call | Instantiates the testing module? | |---|---| | `TestBed.configureTestingModule(...)` | No | | `TestBed.override*(...)` | No | | `await TestBed.compileComponents()` | No — it compiles and resolves resources | | `TestBed.inject(Token)` | **Yes** | | `TestBed.createComponent(Type)` | **Yes** (it injects internally first) | | `TestBed.runInInjectionContext(fn)` | **Yes** | The official guide goes further and says not to reconfigure after `compileComponents` either; treat it as the last configuration step. ## Order among the configuration calls Before instantiation, order is flexible: - `configureTestingModule` can be called **more than once**; its `imports`, `providers` and `declarations` accumulate. - When two providers target the same token, the **last one registered wins**, as in any Angular injector. - `overrideProvider` wins over any ordinary provider for the token, wherever it is declared, regardless of call order. - `overrideComponent` can be called several times for the same component; each override is applied in turn. So "override order" is rarely about the override methods relative to each other. It is about overrides relative to the **first instantiating call**. ## The nested-describe trap Test runners run `beforeEach` hooks outermost first. A shared outer hook that reads a service quietly freezes the module before an inner hook can customise it: ```ts describe('ShiftBanner', () => { let clock: Clock; beforeEach(() => { TestBed.configureTestingModule({imports: [ShiftBanner], providers: [Clock]}); clock = TestBed.inject(Clock); // instantiates the testing module }); describe('in the morning', () => { beforeEach(() => { // throws: the module was instantiated by the outer hook TestBed.overrideProvider(Clock, {useValue: morningClock}); }); }); }); ``` Fixes, in order of preference: - Keep outer hooks to **configuration only**; move `inject`/`createComponent` into the tests or the innermost hook. - Or build a small setup function that takes options (`setup({ clock: morningClock })`) and does configure → override → create in one place. ## Reading the error when it happens The message names the method that was refused and hints at the cause: "Cannot configure the test module when the test module has already been instantiated. Make sure you are not using `inject` before `TestBed.configureTestingModule`." To find the culprit: - Look for any `TestBed.inject`, `createComponent` or `runInInjectionContext` in a hook that runs **before** the failing line — usually an outer `beforeEach` or a shared helper. - Check helpers that look harmless, such as a function that returns `TestBed.inject(Router)` for convenience. - Remember that a `beforeEach` in a parent `describe` runs for every nested test, even ones that never use its result. ## Where compileComponents fits `compileComponents()` is asynchronous. It compiles queued declarables and waits for anything that needs asynchronous work: - **Asynchronous component metadata**, which is what `@defer` blocks produce: the deferred dependencies are loaded lazily, and TestBed must resolve them before it can apply overrides and compile. - **External resources** (`templateUrl`, `styleUrl`) that the build did not inline, which happens in JIT setups outside the CLI's builders. The current testing guide states that `TestBed.compileComponents` is only required when `@defer` blocks are used in the tested components. The CLI's generated spec still calls it after `configureTestingModule` out of habit; that is harmless. When you do need it, it goes **after** all overrides and **before** the first instantiating call: ```ts beforeEach(async () => { TestBed.configureTestingModule({imports: [ReportPage]}); TestBed.overrideProvider(ReportApi, {useValue: fakeReportApi}); await TestBed.compileComponents(); }); ``` If you skip it for a component whose metadata is still unresolved, `createComponent` throws and tells you to call `await TestBed.compileComponents()` first. ## The canonical order 1. `configureTestingModule` (possibly several times across hooks). 2. `override*` calls. 3. `await compileComponents()` when a tested component uses `@defer`. 4. `TestBed.inject(...)` / `TestBed.createComponent(...)`. Everything in steps 1–3 is reset around the next test, so each test repeats the sequence from a clean slate.

  • Two beforeEach hooks both call configureTestingModule with a provider for the same token. Which one does the component get?
    Both calls accumulate into one testing module, and when two ordinary providers target the same token the last one registered wins. Outer hooks run before inner ones, so the inner hook's provider is the one resolved. An `overrideProvider` for that token would win over both.
  • What error do you see if a component with unresolved @defer dependencies is created without compileComponents?
    `createComponent` throws, saying the component has unresolved metadata and asking you to call `await TestBed.compileComponents()` before running the test. Awaiting it after all overrides resolves the deferred dependencies so TestBed can compile the component.

saying these in an interview costs you the question

  • Overrides can be applied at any point in a test and take effect immediately.
  • compileComponents instantiates the testing module, just like TestBed.inject.
  • compileComponents must be awaited in every standalone component test.
  • A second configureTestingModule call replaces the first one's providers.
  • An outer beforeEach can safely inject services before inner hooks override them.