Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Testing basics

A test is code that checks other code: it creates a service or component, performs an action and compares the result with the expected one. Tests run in seconds and show at once when a change breaks something. For every file created with ng generate, the CLI has already made a test skeleton ending in .spec.ts.

Running tests

ng test

The command builds the tests and runs them, and again after every save. Angular 20 uses Karma and Jasmine by default, with tests running in a browser. In Angular 21 and newer, new projects use Vitest. The describe, it and expect functions look the same in simple tests.

Testing a service

Open src/app/todo-store.spec.ts and describe the behavior of the task store:

// src/app/todo-store.spec.ts
import { TestBed } from '@angular/core/testing';
import { TodoStore } from './todo-store';
 
describe('TodoStore', () => {
  let store: TodoStore;
 
  beforeEach(() => {
    store = TestBed.inject(TodoStore);
  });
 
  it('adds a task', () => {
    store.add('Buy bread');
    expect(store.todos().length).toBe(1);
    expect(store.todos()[0].title).toBe('Buy bread');
  });
 
  it('counts open tasks', () => {
    store.add('First');
    store.add('Second');
    store.toggle(store.todos()[0].id);
    expect(store.left()).toBe(1);
  });
});

describe groups tests, it is a single test, and expect(...).toBe(...) is a check. TestBed is Angular's test environment; its inject() method returns a service just like inject() in a component. A new environment is created for every test, so the tasks from the first test do not leak into the second.

The terminal shows a report: passed tests are marked green, failed ones red, with the expected and actual values.

Testing a component

A component is created with TestBed.createComponent(), and the resulting HTML is checked:

// src/app/todo-item/todo-item.spec.ts
import { TestBed } from '@angular/core/testing';
import { TodoItem } from './todo-item';
 
describe('TodoItem', () => {
  it('shows the task title', () => {
    TestBed.configureTestingModule({ imports: [TodoItem] });
    const fixture = TestBed.createComponent(TodoItem);
 
    fixture.componentRef.setInput('todo', { id: 1, title: 'Call Oleg', done: false });
    fixture.detectChanges();
 
    const el: HTMLElement = fixture.nativeElement;
    expect(el.textContent).toContain('Call Oleg');
  });
});

setInput() sets an input the way a parent would. detectChanges() asks Angular to update the template, and nativeElement is the component's root element, where you can look for text and tags.

What to test

WhatExample check
Service logicAfter add() the list has one more task
Calculationsleft() counts only open tasks
Component outputThe task title is in the HTML
EventsAfter a click on the checkbox the toggle output fired

Start with services: they hold the most logic and are the easiest to test.

Common mistake

Forgetting fixture.detectChanges() after setInput() or a data change. The template stays in its old state, and the text check fails even though the component works correctly. After every data change in a component test, call detectChanges().

Thanks for reading!

Donate to the site — we work hard for you.