Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Perfect choice 🙌 — Angular testing can feel intimidating if presented only in theory. A practical, hands-on testing chapter will give your readers confidence by showing them how to actually write and run tests for components, services, and full applications.

Here’s a book-style draft for Chapter 18: Testing Angular Applications (Practical Guide).


Chapter 18: Testing Angular Applications

Writing tests is one of the most valuable investments in software development. Angular provides a rich testing ecosystem out of the box, using Jasmine (test framework) and Karma (test runner). Many teams also use Jest and Cypress for modern workflows.

In this chapter, we’ll focus on practical setups:

  • Unit testing components and services.
  • Testing HTTP requests.
  • Writing end-to-end (E2E) tests.
  • Tips for faster, maintainable testing.

18.1 Angular Testing Setup

When you generate a project with the Angular CLI, testing support is already included:

  • Jasmine for writing specs.
  • Karma for running tests in a browser.
  • Test files use the .spec.ts suffix.

To run tests:

ng test

18.2 Unit Testing a Service

Let’s start with a simple service.

Service:

@Injectable({ providedIn: 'root' })
export class CalculatorService {
  add(a: number, b: number) { return a + b; }
  subtract(a: number, b: number) { return a - b; }
}

Test:

import { TestBed } from '@angular/core/testing';
import { CalculatorService } from './calculator.service';

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

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

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

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

18.3 Unit Testing a Component

Component:

@Component({
  selector: 'app-greeter',
  standalone: true,
  template: `<p>Hello {{ name }}</p>`
})
export class GreeterComponent {
  @Input() name = 'Angular';
}

Test:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GreeterComponent } from './greeter.component';

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

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

    fixture = TestBed.createComponent(GreeterComponent);
  });

  it('should render default name', () => {
    fixture.detectChanges();
    expect(fixture.nativeElement.textContent).toContain('Hello Angular');
  });

  it('should render input name', () => {
    fixture.componentInstance.name = 'World';
    fixture.detectChanges();
    expect(fixture.nativeElement.textContent).toContain('Hello World');
  });
});

18.4 Testing Services with HTTP

Angular provides the HttpTestingController to mock HTTP requests.

Service:

@Injectable({ providedIn: 'root' })
export class UserService {
  constructor(private http: HttpClient) {}
  getUsers() { return this.http.get<any[]>('/api/users'); }
}

Test:

import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { UserService } from './user.service';

describe('UserService', () => {
  let service: UserService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule]
    });
    service = TestBed.inject(UserService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  it('should fetch users', () => {
    const mockUsers = [{ name: 'Alice' }, { name: 'Bob' }];

    service.getUsers().subscribe(users => {
      expect(users).toEqual(mockUsers);
    });

    const req = httpMock.expectOne('/api/users');
    expect(req.request.method).toBe('GET');
    req.flush(mockUsers);

    httpMock.verify();
  });
});

18.5 End-to-End Testing with Cypress

Angular CLI used to ship with Protractor, but most teams now use Cypress for E2E.

Install Cypress:

npm install cypress --save-dev

Add a test (cypress/e2e/app.cy.ts):

describe('My First Test', () => {
  it('visits the app root', () => {
    cy.visit('http://localhost:4200');
    cy.contains('Hello Angular');
  });
});

Run Cypress:

npx cypress open

This opens the Cypress test runner with a browser simulation.


18.6 Tips for Better Angular Tests

  • Use TestBed for components and services to get DI and Angular environment.
  • Keep unit tests small — one assertion per test is best.
  • Mock external APIs (use HttpTestingController, or libraries like jest-mock).
  • Use async/await in tests for better readability with asynchronous code.
  • Prefer Jest over Karma if you want faster, modern testing (drop-in replacement for Jasmine).
  • Test behavior, not implementation details.

18.7 Summary

  • Angular testing is built-in with Jasmine + Karma.
  • Use TestBed for unit testing components and services.
  • Mock HTTP with HttpTestingController.
  • For end-to-end tests, prefer Cypress over Protractor.
  • Keep tests small, focused, and meaningful.