← Back to Chapters

Angular Component Unit Testing

? Angular Component Unit Testing

? Quick Overview

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.

? Key Concepts

  • TestBed: Angular testing utility used to configure a test module and create components.
  • ComponentFixture: Handles access to the component instance and its rendered template.
  • Change Detection: Calling fixture.detectChanges() updates the view with the latest data.
  • DOM Access: Use fixture.nativeElement to query and assert rendered HTML.
  • EventEmitter: Test @Output() properties by spying on their emit() calls.

? Syntax & Testing Flow

Typical steps for component unit testing in Angular:

  1. Configure the testing module with TestBed.configureTestingModule().
  2. Create the component using TestBed.createComponent() to get a ComponentFixture.
  3. Access the component instance via fixture.componentInstance.
  4. Trigger change detection with fixture.detectChanges() whenever inputs or state change.
  5. Read and assert DOM content using fixture.nativeElement or query selectors.
  6. For outputs, spy on EventEmitter. Verify that emit() is called with expected values.

? Code Examples

1️⃣ Testing Component Creation & Template Rendering

? View Code Example
// 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!');
  });
});

2️⃣ Testing Event Emission from a Component

? View Code Example
// 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);
  });
});

? Live Output / Explanation

What These Tests Prove

  • In HelloComponent, the first test confirms that the component is created successfully. The second test sets name = 'Angular', runs fixture.detectChanges(), and then checks that the rendered paragraph contains Hello, Angular!.
  • In CounterComponent, the test spies on countChanged.emit. After calling increment(), the spec expects that emit() was called with the value 1, confirming both the state update and event emission.
  • Together, these tests validate input binding (via @Input()), DOM rendering, internal state updates, and output events (via @Output() and EventEmitter).

✅ Tips & Best Practices

  • Always call fixture.detectChanges() after updating inputs or component state to refresh the template.
  • Isolate components by mocking dependent services to keep tests fast and reliable.
  • Write separate tests for different behaviors instead of testing everything in a single spec.
  • Use clear and descriptive test names so failures are easy to understand.
  • Spy on EventEmitter outputs to ensure they emit the correct payloads for each user interaction.

? Try It Yourself

  • Create a component with multiple @Input() and @Output() properties and write unit tests for each.
  • Build a component with a button that updates a template property and emits an event; test both the DOM update and the emitted value.
  • Practice using fixture.detectChanges() at different points in your tests and observe how it affects the rendered template.
  • Refactor your tests to extract common setup logic into beforeEach() blocks for cleaner code.