In Angular, what does TestBed.createComponent return, and how do you use that fixture to render and query the component?
answer
- a handle around the created component
- class instance versus host element
- nothing renders synchronously
- By.css through the debug tree
basics
~10 sTestBed.createComponent returns a ComponentFixture: a handle exposing the component instance, its host element and a DebugElement tree. You let it render with await fixture.whenStable() or fixture.detectChanges(), then query nativeElement or debugElement.query(By.css(...)).
solid answer
~30 s`TestBed.createComponent(LimitedCounter)` returns a `ComponentFixture<LimitedCounter>`. It exposes `componentInstance` (the class), `componentRef` (for `setInput`), `nativeElement` (the host DOM element), `debugElement` (Angular's queryable wrapper) and methods that drive change detection. Creating the component does **not** render its template synchronously, so you first `await fixture.whenStable()` — the default in zoneless tests — or call `fixture.detectChanges()`. Then query the DOM: `fixture.nativeElement.querySelector('.count')`, or `fixture.debugElement.query(By.css('.count'))`, which returns a `DebugElement` or `null`, and `By.directive(Child)` to find child components.
code
ts · 14 linesimport {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {LimitedCounter} from './limited-counter';
it('renders the count and the button', async () => {
const fixture = TestBed.createComponent(LimitedCounter);
await fixture.whenStable();
const count = fixture.debugElement.query(By.css('.count'));
const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
expect(count.nativeElement.textContent).toBe('0');
expect(button.disabled).toBe(false);
});go deeper
Know the fixture's main members, that you must let it render before asserting, and the two ways to query: nativeElement.querySelector and debugElement.query(By.css).
Explain when debugElement is worth it over nativeElement: By.directive, element injectors and triggerEventHandler, plus how query and queryAll behave on no match.
Keep assertions on rendered output rather than instance internals, and build small helpers (possibly via getLastFixture) so specs read as user actions.
Set conventions for which query layer specs use, fixture APIs, CDK harnesses or user-facing queries, and when each is acceptable in review.
## What a fixture is A **`ComponentFixture<T>`** is what `TestBed.createComponent(T)` hands back after creating a component inside the testing module. It is a test-only wrapper around three things Angular created for you: the component instance, the host element it rendered into, and a `ComponentRef` that controls it. Every component test starts by holding one. The running example is a counter with a limit: ```ts import {Component, input, output, signal} from '@angular/core'; @Component({ selector: 'app-limited-counter', template: ` <span class="count">{{ count() }}</span> <button type="button" (click)="increment()" [disabled]="count() >= max()">+</button> `, }) export class LimitedCounter { readonly max = input(3); readonly count = signal(0); readonly limitReached = output<number>(); increment(): void { this.count.update((c) => c + 1); if (this.count() === this.max()) { this.limitReached.emit(this.count()); } } } ``` ## The fixture's members | Member | What it gives you | Typical use | |---|---|---| | `componentInstance` | The `LimitedCounter` class instance | Read `count()`, call `increment()` | | `componentRef` | The `ComponentRef` | `componentRef.setInput('max', 5)` | | `nativeElement` | The host DOM element | `querySelector`, `textContent`, real `click()` | | `debugElement` | A `DebugElement` for the host | `query(By.css(...))`, `injector`, `triggerEventHandler` | | `changeDetectorRef` | The host view's change detector | Rarely needed directly | | `detectChanges()` | Runs change detection now | Older and zone-based tests | | `whenStable()` | Promise that settles when the app is stable | The default wait in zoneless tests | | `destroy()` | Destroys the component | Testing `ngOnDestroy` / `DestroyRef` cleanup | ## Rendering: nothing happens synchronously `TestBed.createComponent` creates the component but does **not** run change detection synchronously. Immediately after it, the `<span class="count">` is not in the DOM yet. You have to let Angular render: - `await fixture.whenStable()` — the pattern the current guide and CLI template use for zoneless tests (the default for new projects since v21). - `fixture.detectChanges()` — runs change detection synchronously; common in older, zone-based suites. ```ts it('starts at zero', async () => { const fixture = TestBed.createComponent(LimitedCounter); await fixture.whenStable(); expect(fixture.nativeElement.querySelector('.count').textContent).toBe('0'); }); ``` ## Querying: nativeElement or debugElement Two routes reach the rendered DOM: 1. **`nativeElement`** is the host element itself. Standard DOM APIs work: `querySelector`, `querySelectorAll`, `textContent`, `click()`. 2. **`debugElement`** wraps the same tree in Angular's `DebugElement`, which adds framework knowledge: - `query(By.css('.count'))` returns the **first** matching `DebugElement`, or `null` when nothing matches. - `queryAll(By.css('button'))` returns an array, empty when nothing matches. - `By.directive(LimitedCounter)` matches elements where a given component or directive is present — handy for finding a child component and reading its `componentInstance`. - `.nativeElement` on any `DebugElement` gets you back to the DOM node. - `.injector` gives the element's injector, which is how you reach a service a component provides for itself. `By` is imported from `@angular/platform-browser`. In browser-based tests, `nativeElement` is enough for most assertions; `debugElement` earns its place when you need `By.directive`, the element injector, or `triggerEventHandler`. ## Getting the fixture back later Angular 22 added `TestBed.getLastFixture()`, which returns the most recently created `ComponentFixture` in the current test and throws "No fixture has been created yet." if there is none. It helps shared helpers that act on "the component under test" without threading the fixture through every call. ## Destroying and cleanup `fixture.destroy()` destroys the component: `ngOnDestroy` runs, `DestroyRef.onDestroy` callbacks fire, and subscriptions tied to them end. That makes cleanup testable — create the fixture, destroy it, then assert that a timer stopped or a service was told to unsubscribe. You rarely need to call it for hygiene: TestBed destroys every active fixture when it resets between tests. A test can create **more than one** fixture, for instance to check two counters with different limits side by side. Each fixture is independent: its own component instance, its own `componentRef`, and its own host element. ## Mistakes interviewers listen for - Asserting on the DOM straight after `createComponent`, before any render. - Treating `query(By.css(...))` as returning an array, or expecting it to throw on no match. - Reaching into private fields of `componentInstance` instead of asserting on what renders. - Forgetting that `nativeElement` is the **host** element, so `querySelector` searches inside it.
- How would you get the instance of a child component rendered inside the component under test?Query for it by type: `fixture.debugElement.query(By.directive(ChildComponent))` returns the child's host `DebugElement`, and its `componentInstance` is the child class. `queryAll(By.directive(...))` returns every instance. That lets you assert what the parent passed in without depending on the child's markup.
- What does TestBed.getLastFixture() do, and when is it useful?Added in Angular 22, it returns the most recently created `ComponentFixture` in the current test, and throws if none exists yet. It is useful in shared helpers, such as a `clickButton(label)` utility, that need the fixture without it being passed through every call.
saying these in an interview costs you the question
- TestBed.createComponent renders the template synchronously, so you can assert immediately.
- debugElement.query returns an empty array when nothing matches.
- nativeElement is the component class instance.
- By.css is imported from @angular/core/testing.
- You must read private component fields to verify what the user sees.