← Back to Chapters

Angular Unit Testing Basics

? Angular Unit Testing Basics

? Quick Overview

Unit testing in Angular focuses on verifying that individual building blocks like components, services, and pipes work correctly in isolation. Angular uses Jasmine for writing test specs and Karma as the test runner that executes these tests in a browser.

Well-written unit tests make your Angular app more reliable, easier to refactor, and safer to extend with new features.

? Key Concepts

  • Unit: The smallest testable piece of code (service method, component class, pipe, etc.).
  • Test Suite (describe()): Groups related tests together.
  • Spec (it()): A single test that checks one behavior.
  • Expectation (expect()): Asserts the actual result against the expected result.
  • TestBed: Angular’s primary API for configuring and initializing the testing environment.
  • Fixtures: Wrappers around components to access the instance, template, and trigger change detection.

? Syntax & Theory (Jasmine + Angular TestBed)

At the core of Angular unit testing is the combination of Jasmine’s BDD-style syntax and Angular’s TestBed utility:

  • describe() defines a test suite (what is being tested).
  • beforeEach() sets up the test environment before every test.
  • it() defines an individual spec (a single behavior or rule).
  • expect() creates an assertion about how the code should behave.
  • TestBed.configureTestingModule() configures an Angular testing module for your tests.
? View Code Example
// Basic Jasmine test structure with Angular
import { TestBed } from '@angular/core/testing';
import { MyService } from './my.service';

describe('MyService', () => {
  let service: MyService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(MyService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });
});

? Unit Test for an Angular Service

Services usually contain business logic or data-fetching logic. Unit tests for services verify that the exposed methods return the expected values.

? View Code Example
// data.service.ts - service that returns sample data
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  getData(): string {
    return 'Hello Angular';
  }
}

// data.service.spec.ts - unit tests for DataService
import { TestBed } from '@angular/core/testing';
import { DataService } from './data.service';

describe('DataService', () => {
  let service: DataService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(DataService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should return correct data', () => {
    expect(service.getData()).toBe('Hello Angular');
  });
});

? Unit Test for an Angular Component

Components combine template + logic. In unit tests, we often verify that the component class behaves correctly and that the template reflects the class state.

? View Code Example
// app.component.ts - simple component with a title
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: '<h1>{{ title }}</h1>'
})
export class AppComponent {
  title = 'Unit Testing Demo';
}

// app.component.spec.ts - unit tests for AppComponent
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
  let component: AppComponent;
  let fixture: ComponentFixture<AppComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [AppComponent]
    }).compileComponents();

    fixture = TestBed.createComponent(AppComponent);
    component = fixture.componentInstance;
  });

  it('should create the app', () => {
    expect(component).toBeTruthy();
  });

  it('should have title "Unit Testing Demo"', () => {
    expect(component.title).toBe('Unit Testing Demo');
  });
});

? Live Output / Explanation

What happens when you run these tests?

  • Karma starts a browser and executes all Jasmine specs it discovers (files ending with .spec.ts).
  • For DataService, Jasmine will:
    • Configure the testing module with TestBed.configureTestingModule().
    • Inject an instance of DataService using TestBed.inject().
    • Verify that the service instance exists and that getData() returns 'Hello Angular'.
  • For AppComponent, Angular will:
    • Create a test module with AppComponent declared.
    • Build a ComponentFixture to host the component in a test environment.
    • Confirm that the component instance is created and that the title property matches the expected text.
  • If all expectations pass, the test run is marked as successful (green). Any failed expectation shows which spec failed and why.

? Common Use Cases

  • Validating that service methods return correct values or transform data correctly.
  • Ensuring components correctly bind and display data from their class properties.
  • Checking that edge cases (empty states, invalid inputs) behave as expected.
  • Preventing regressions when refactoring or adding new features to existing code.

✅ Tips & Best Practices

  • Keep each test focused on a single behavior or rule.
  • Mock dependencies (HTTP, services, etc.) to keep tests fast and deterministic.
  • Use descriptive test names that clearly describe the expected behavior.
  • Prefer testing public APIs of classes instead of private implementation details.
  • Run tests frequently while developing to catch issues early.

? Try It Yourself / Practice Tasks

  • Create a new service with multiple methods and write unit tests for each method.
  • Build a component with @Input() properties and write tests to verify that input values are rendered correctly in the template.
  • Mock a service dependency in a component test using a fake class or Jasmine spies, and assert that methods are called with the right arguments.
  • Add a new property to AppComponent (e.g., subtitle) and extend the tests to verify both title and subtitle.