Component unit testing in Angular focuses on testing a single component in isolation. It verifies: how the template is rendered, how @Input() and @Output() bindings behave, and how the component interacts with Angular features like change detection and the DOM.
fixture.detectChanges() updates the view with the latest data.fixture.nativeElement to query and assert rendered HTML.@Output() properties by spying on their emit() calls.Typical steps for component unit testing in Angular:
TestBed.configureTestingModule().TestBed.createComponent() to get a ComponentFixture.fixture.componentInstance.fixture.detectChanges() whenever inputs or state change.fixture.nativeElement or query selectors.EventEmitter. Verify that emit() is called with expected values.
// hello.component.ts - simple greeting component
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-hello',
template: '<p>Hello, {{ name }}!</p>'
})
export class HelloComponent {
@Input() name: string = '';
}
// hello.component.spec.ts - unit tests for HelloComponent
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HelloComponent } from './hello.component';
describe('HelloComponent', () => {
let component: HelloComponent;
let fixture: ComponentFixture<HelloComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [HelloComponent]
}).compileComponents();
fixture = TestBed.createComponent(HelloComponent);
component = fixture.componentInstance;
});
it('should create the component', () => {
expect(component).toBeTruthy();
});
it('should render the name input correctly', () => {
component.name = 'Angular';
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('p')?.textContent).toContain('Hello, Angular!');
});
});
// counter.component.ts - emits count changes via @Output()
import { Component, EventEmitter, Output } from '@angular/core';
@Component({
selector: 'app-counter',
template: '<button (click)="increment()">Increment</button>'
})
export class CounterComponent {
count = 0;
@Output() countChanged = new EventEmitter<number>();
increment() {
this.count++;
this.countChanged.emit(this.count);
}
}
// counter.component.spec.ts - unit tests for CounterComponent
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CounterComponent } from './counter.component';
describe('CounterComponent', () => {
let component: CounterComponent;
let fixture: ComponentFixture<CounterComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [CounterComponent]
}).compileComponents();
fixture = TestBed.createComponent(CounterComponent);
component = fixture.componentInstance;
});
it('should emit countChanged on increment', () => {
spyOn(component.countChanged, 'emit');
component.increment();
expect(component.countChanged.emit).toHaveBeenCalledWith(1);
});
});
name = 'Angular', runs fixture.detectChanges(), and then checks that the rendered paragraph contains Hello, Angular!.countChanged.emit. After calling increment(), the spec expects that emit() was called with the value 1, confirming both the state update and event emission.@Input()), DOM rendering, internal state updates, and output events (via @Output() and EventEmitter).fixture.detectChanges() after updating inputs or component state to refresh the template.EventEmitter outputs to ensure they emit the correct payloads for each user interaction.@Input() and @Output() properties and write unit tests for each.fixture.detectChanges() at different points in your tests and observe how it affects the rendered template.beforeEach() blocks for cleaner code.