Component tests verify that a template renders correctly and responds to user interaction and input changes. This lesson covers ComponentFixture, detectChanges(), and querying the DOM inside a real browser via Karma.
The ComponentFixture
TestBed.createComponent() returns a ComponentFixture, a wrapper giving you access to the component instance itself, its rendered DOM (nativeElement), and control over Angular's change detection cycle via detectChanges().
import { TestBed } from '@angular/core/testing';
import { GreetingComponent } from './greeting.component';
describe('GreetingComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [GreetingComponent]
}).compileComponents();
});
it('renders the provided name', () => {
const fixture = TestBed.createComponent(GreetingComponent);
fixture.componentInstance.name = 'Ada';
fixture.detectChanges();
const el = fixture.nativeElement as HTMLElement;
expect(el.querySelector('h1')?.textContent).toContain('Ada');
});
});
fixture.detectChanges() triggers Angular to re-render the template based on the component's current state — without it, DOM assertions would see stale content.
The ComponentFixture API
fixture.componentInstance // the actual component class instance
fixture.nativeElement // the rendered root DOM element (real browser DOM via Karma)
fixture.detectChanges() // triggers Angular's change detection / re-render
fixture.debugElement // Angular's wrapped element, useful for querying by directive
nativeElement is a real DOM element because Karma runs the test in an actual browser — no jsdom approximation involved.
detectChanges() must be called after changing component state directly, or the template won't reflect it yet.
debugElement.query(By.css('selector')) is the Angular-idiomatic way to find elements, layering extra metadata over plain DOM queries.
Calling detectChanges() too rarely is one of the most common sources of 'my assertion sees stale content' failures.
Component Testing Cheat Sheet
The essentials for testing an Angular component's rendered output and behavior.
Task
Approach
Create the fixture
TestBed.createComponent(MyComponent)
Set an input directly
fixture.componentInstance.someInput = value
Re-render after a change
fixture.detectChanges()
Query rendered DOM
fixture.nativeElement.querySelector(...)
Simulate a click
element.dispatchEvent(new Event('click')) then detectChanges()
Because Karma runs a real browser, you can dispatch genuine DOM events — clicks, input changes, keyboard events — exactly as a real user would trigger them, then call detectChanges() to let Angular process the resulting state change.
Real .click() on a real DOM element is available precisely because this test runs inside an actual browser via Karma.
Testing @Input() and @Output() Bindings
Component inputs can be set directly on componentInstance; outputs (EventEmitter) can be subscribed to directly in the test to assert they emitted the expected value.
it('emits selected id on row click', () => {
const fixture = TestBed.createComponent(UserRowComponent);
fixture.componentInstance.user = { id: 7, name: 'Ada' };
let emittedId: number | undefined;
fixture.componentInstance.selected.subscribe((id: number) => (emittedId = id));
fixture.detectChanges();
fixture.nativeElement.querySelector('tr').click();
expect(emittedId).toBe(7);
});
Common Mistakes
Forgetting to call detectChanges() after changing component state, then asserting against stale rendered DOM.
Using plain DOM queries when debugElement's directive-aware queries would be clearer for component-specific lookups.
Not resetting a component's state between assertions within the same spec, leaking state across checks.
Testing implementation details of the component class instead of its actual rendered output and emitted events.
Key Takeaways
TestBed.createComponent() returns a ComponentFixture wrapping the component instance and its real rendered DOM.
detectChanges() must be called to reflect state changes in the rendered template.
Because Karma runs a real browser, genuine DOM events (.click(), etc.) can be dispatched directly in tests.
Inputs are set directly on the component instance; outputs are asserted by subscribing to the EventEmitter.
Pro Tip
If a component test behaves unexpectedly, add an extra fixture.detectChanges() call before your assertion as a quick diagnostic step. If that alone fixes it, you've found a missing change-detection trigger rather than a real bug in the component.
You now understand component testing with Karma. Next, learn how to test Angular services in isolation.