On this page

Basic testing with Vitest

12 min read TextCh. 5 — Production

Testing in Angular

Automated testing verifies that your code works correctly and prevents regressions. Angular 21 supports Vitest as a modern and fast test runner.

Types of tests

Type What it tests Speed Example
Unit An isolated function/class Very fast Services, pipes, utils
Component A component with template Fast Rendering, interaction
Integration Multiple components together Medium User flows
E2E The complete app in a browser Slow Registration, login, purchase

Configuring Vitest

Angular 21 can use Vitest instead of Karma/Jasmine:

npm install -D vitest @analogjs/vitest-angular jsdom

Configuration in vitest.config.ts:

import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    environment: 'jsdom',
    include: ['src/**/*.spec.ts'],
    setupFiles: ['src/test-setup.ts'],
  },
});

TestBed

TestBed is Angular's testing environment. It configures a testing module with the necessary providers:

import { TestBed } from '@angular/core/testing';

beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [MyService],
  });
});

Testing services

Services are the easiest to test because they are pure classes:

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

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

  it('should add two numbers', () => {
    expect(service.add(2, 3)).toBe(5);
  });

  it('should handle negative numbers', () => {
    expect(service.add(-1, 5)).toBe(4);
  });
});

Testing signals

Signals are tested by reading their value directly:

it('should update the signal', () => {
  const service = TestBed.inject(CounterService);

  expect(service.value()).toBe(0);

  service.increment();
  expect(service.value()).toBe(1);

  // Computed signals too
  expect(service.isEven()).toBe(false);
});

Testing pipes

Pipes are pure functions, ideal for testing:

import { TruncatePipe } from './truncate.pipe';

describe('TruncatePipe', () => {
  const pipe = new TruncatePipe();

  it('should truncate long text', () => {
    const text = 'This is a fairly long text for the test';
    expect(pipe.transform(text, 20)).toBe('This is a fairly lon...');
  });

  it('should not truncate short text', () => {
    expect(pipe.transform('Hello', 20)).toBe('Hello');
  });

  it('should handle empty string', () => {
    expect(pipe.transform('', 20)).toBe('');
  });
});

Testing components

For components, create a fixture that renders the component:

import { ComponentFixture, TestBed } from '@angular/core/testing';

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

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [Counter],
    }).compileComponents();

    fixture = TestBed.createComponent(Counter);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should render the initial value', () => {
    const element = fixture.nativeElement;
    expect(element.textContent).toContain('0');
  });
});

Mocks and stubs

To isolate tests, use dependency mocks:

const mockAuth = {
  user: signal(null),
  isAuthenticated: computed(() => false),
  login: vi.fn(),
};

TestBed.configureTestingModule({
  providers: [
    MyService,
    { provide: AuthService, useValue: mockAuth },
  ],
});

Testing best practices

  1. One assert per test — Each test verifies a single thing
  2. Descriptive namesshould increment the value when increment() is called
  3. Arrange-Act-Assert — Clear structure in each test
  4. Test behavior — Not internal implementation
  5. Start with services — They are the easiest and most valuable

Practice

  1. Test a service with signals: Create a NotesService with add(), remove() methods and a computed() for the total. Write at least 3 unit tests that verify the signal behavior.
  2. Test a pipe: Implement a capitalize pipe and write tests for normal text, empty text, and text with multiple words.
  3. Use mocks in a test: Create a test for a service that depends on AuthService. Use a mock with { provide: AuthService, useValue: mockAuth } to isolate the dependency.

In the next lesson, you will build a complete project that integrates everything you have learned.

Test logic, not templates
Focus on testing services and business logic first. Component tests with templates are more fragile and expensive to maintain. A well-tested service is more valuable than many UI tests.
Arrange-Act-Assert
Structure your tests in three phases: Arrange (prepare data), Act (execute the action), and Assert (verify results). This makes tests clear and easy to read.
// --- counter.service.ts ---
import { Injectable, signal, computed } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class CounterService {
  private readonly _value = signal(0);
  readonly value = this._value.asReadonly();
  readonly isEven = computed(() => this._value() % 2 === 0);

  increment(): void {
    this._value.update(v => v + 1);
  }

  decrement(): void {
    this._value.update(v => Math.max(0, v - 1));
  }

  reset(): void {
    this._value.set(0);
  }
}

// --- counter.service.spec.ts ---
import { describe, it, expect, beforeEach } from 'vitest';
import { TestBed } from '@angular/core/testing';
import { CounterService } from './counter.service';

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

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

  it('should start at 0', () => {
    expect(service.value()).toBe(0);
  });

  it('should increment the value', () => {
    service.increment();
    service.increment();
    expect(service.value()).toBe(2);
  });

  it('should not decrement below 0', () => {
    service.decrement();
    expect(service.value()).toBe(0);
  });

  it('isEven should derive correctly', () => {
    expect(service.isEven()).toBe(true);
    service.increment();
    expect(service.isEven()).toBe(false);
    service.increment();
    expect(service.isEven()).toBe(true);
  });

  it('should reset to 0', () => {
    service.increment();
    service.increment();
    service.reset();
    expect(service.value()).toBe(0);
  });
});