Testing Angular Apps
Set up unit tests for components and services with TestBed and Jasmine/Jest, and end-to-end tests with Playwright or Cypress.
Levels of Testing
Unit Tests
Test one component or service in isolation — fast and focused.
Integration Tests
Test how several pieces work together, like a component and an injected service.
End-to-End Tests
Drive a real browser through your app exactly as a user would.
Testing a Service
A plain service with no Angular-specific dependencies can be tested by simply instantiating it directly, no special tooling required.
import { CartStore } from './cart-store.service';
describe('CartStore', () => { it('adds an item to the cart', () => { const store = new CartStore(); store.addItem({ id: 1, name: 'Mouse', price: 25 }); expect(store.cartItems()).toHaveSize(1); });});Testing a Component with TestBed
TestBed configures an isolated Angular testing module, letting you create and inspect a real component instance along with its rendered template.
import { TestBed } from '@angular/core/testing';import { CounterComponent } from './counter.component';
describe('CounterComponent', () => { it('renders the initial count', () => { const fixture = TestBed.createComponent(CounterComponent); fixture.detectChanges();
const text = fixture.nativeElement.textContent; expect(text).toContain('Clicked 0 times'); });});Simulating User Interaction
it('increments the count when clicked', () => { const fixture = TestBed.createComponent(CounterComponent); fixture.detectChanges();
const button = fixture.nativeElement.querySelector('button'); button.click(); fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Clicked 1 times');});Angular doesn't automatically re-render a test fixture after a state change — call fixture.detectChanges() after any action, before asserting on the resulting DOM output.
Mocking HttpClient
Angular's HttpClientTestingModule intercepts HTTP requests during tests, letting you assert on what was requested and control exactly what response comes back, without hitting a real network.
import { TestBed } from '@angular/core/testing';import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';import { provideHttpClient } from '@angular/common/http';import { PostsService } from './posts.service';
it('fetches posts', () => { TestBed.configureTestingModule({ providers: [PostsService, provideHttpClient(), provideHttpClientTesting()], });
const service = TestBed.inject(PostsService); const httpMock = TestBed.inject(HttpTestingController);
service.getPosts().subscribe(posts => { expect(posts.length).toBe(1); });
const req = httpMock.expectOne('/api/posts'); req.flush([{ id: 1, title: 'Test Post' }]);});End-to-End Testing
Playwright or Cypress drive a real browser against your running app, verifying full flows — navigation, form submission — the way an actual visitor would experience them.
import { test, expect } from '@playwright/test';
test('user can sign up', async ({ page }) => { await page.goto('/signup'); await page.fill('input[name="email"]', 'test@example.com'); await page.click('button[type="submit"]'); await expect(page.getByText('Welcome')).toBeVisible();});FAQs
Yes — every ng generate component (and new project) scaffolds a matching .spec.ts file, using Jasmine and Karma by default (with Jest available as an alternative).
No — a plain service with no Angular dependencies can be tested by direct instantiation; TestBed is specifically for testing Angular-integrated pieces like components.
Summary
TestBed and HttpClientTestingModule cover unit and integration testing, with Playwright or Cypress rounding out full end-to-end coverage. Next, you'll look at performance optimization techniques specific to Angular.