Learn Angular - Testing & Quality
Episode 16 of 24

Learn Angular - Testing & Quality

This episode covers testing and code quality: unit testing components and services, testing pipes, directives, and guards, integration testing with TestBed, and end-to-end testing with Cypress or Playwright.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Untested code is code that can break without anyone noticing. Testing isn't an administrative obligation — it's a safety net that gives teams the confidence to change code.

Episode 16 covers unit testing components and services, testing pipes, directives, and guards, integration testing with TestBed, and end-to-end testing with Cypress or Playwright. You'll build the habit of writing tests alongside production code.

Unit Testing Components and Services

Running Tests

A default Angular project uses Jasmine and Karma:

Run unit tests
ng test

ng test runs all spec files and reports the results in the browser. Test files sit next to their code: produk.component.spec.ts for produk.component.ts.

Unit Testing a Service

Services that don't depend on the browser are the easiest to test:

JSService unit test
import { TestBed } from '@angular/core/testing';
import { KeranjangService } from './keranjang.service';
import { Produk } from './produk.model';
 
describe('KeranjangService', () => {
  let service: KeranjangService;
 
  beforeEach(() => {
    TestBed.configureTestingModule({ providers: [KeranjangService] });
    service = TestBed.inject(KeranjangService);
  });
 
  it('menghitung total harga dengan benar', () => {
    service.tambah({ id: 1, nama: 'Kopi', harga: 25000 } as Produk);
    service.tambah({ id: 2, nama: 'Teh', harga: 15000 } as Produk);
 
    expect(service.totalHarga()).toBe(40000);
    expect(service.totalItem()).toBe(2);
  });
});

TestBed creates an isolated test environment. The test above verifies KeranjangService behavior without touching the UI — fast, reliable, and independent of the browser.

Testing Pipes, Directives, and Guards

Testing Pipes and Directives

A pipe is tested by calling its transform method directly. A directive is tested with a template that uses it:

JSCustom pipe test
import { RupiahPipe } from './rupiah.pipe';
 
describe('RupiahPipe', () => {
  const pipe = new RupiahPipe();
 
  it('memformat angka menjadi Rupiah', () => {
    expect(pipe.transform(25000)).toContain('25.000');
  });
});

Testing a pipe is just instantiating the class and calling transform — no TestBed needed. Directives and guards need a little setup: create a host component for the directive, and call the guard function with stubbed dependencies to check the access decision.

Testing Guards

A guard is an ordinary function, so it's easy to test with a fake AuthService:

JSAuth guard test
import { TestBed } from '@angular/core/testing';
import { authGuard } from './auth.guard';
 
describe('authGuard', () => {
  it('mengizinkan akses saat pengguna login', () => {
    const hasil = TestBed.runInInjectionContext(() => authGuard());
    expect(hasil).toBe(true);
  });
});

runInInjectionContext runs the guard with the TestBed injector so inject inside it works. For the not-logged-in scenario, provide an AuthService stub that returns a different state.

Integration Testing with TestBed

Combining Real Dependencies

Integration tests connect several real parts — for example a component with a real service:

JSIntegration test with a real service
import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';
import { PostService } from './post.service';
 
describe('PostService integration', () => {
  let service: PostService;
  let httpMock: HttpTestingController;
 
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        PostService,
        provideHttpClient(),
        provideHttpClientTesting(),
      ],
    });
    service = TestBed.inject(PostService);
    httpMock = TestBed.inject(HttpTestingController);
  });
 
  it('memanggil endpoint yang benar', () => {
    service.listar().subscribe();
 
    const req = httpMock.expectOne('/api/posts');
    expect(req.request.method).toBe('GET');
    req.flush([]);
  });
});

HttpTestingController captures outgoing requests without actually sending them to the network — tests run fast and deterministically. req.flush([]) simulates the server response.

E2E Testing with Cypress or Playwright

Why E2E

Unit and integration tests verify parts; end-to-end (E2E) tests verify entire user flows in a real browser — from login, navigation, to form submission.

Add Cypress
npm install -D cypress
npx cypress open

Cypress and Playwright are equally powerful. Protractor (the predecessor) is deprecated, so for new projects choose one of the two.

An Example E2E Test

JSLogin E2E test with Cypress
describe('Alur login', () => {
  it('berhasil masuk dan menuju dashboard', () => {
    cy.visit('/login');
    cy.get('[data-cy=email]').type('admin@example.com');
    cy.get('[data-cy=password]').type('rahasia123');
    cy.get('[data-cy=submit]').click();
    cy.url().should('include', '/dashboard');
  });
});

[data-cy=...] selectors are stable, test-specific attributes — better than CSS selectors that often change. E2E tests running in CI give you full confidence that the application's main flows work from the user's perspective.

Wrap Up

Key takeaways:

  • ng test runs Jasmine tests with Karma; spec files sit alongside the code.
  • Services are tested with TestBed.inject; components with a fixture and detectChanges.
  • Pipes are tested directly via transform; guards with runInInjectionContext.
  • HttpTestingController simulates responses without a real network.
  • E2E with Cypress or Playwright verifies user flows in the browser.
  • data-cy selectors make E2E tests stable against UI changes.

In the next episode, episode 17, we'll cover accessibility and UX — ARIA attributes and keyboard navigation, semantic markup and accessible forms, responsive layout and mobile support, and internationalization and localization.

Learn Angular - Testing & Quality | Learn Angular