Testing
Angular is built for testing: dependency injection makes services easy to swap, and TestBed renders components in a test. A few patterns cover most of what you need.
On this page · 6 min · Advanced
On this page
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 6 min
- Difficulty
- Advanced
- Updated
- Oct 28, 2026
Related tutorials
Learning objectives#
- Configure TestBed for a standalone component
- Interact with the DOM and assert results
- Test HTTP services with HttpTestingController
- Replace dependencies with test doubles
Explanation#
ng test runs unit tests (the default runner in Angular 20 projects is Karma with Jasmine; newer CLI versions also support Vitest). Spec files sit next to the code: product-list.spec.ts.
TestBed.configureTestingModule({ imports: [MyComponent], providers: [...] }) prepares a mini app; TestBed.createComponent(MyComponent) renders it. fixture.detectChanges() (or await fixture.whenStable()) applies changes.
Find elements with fixture.nativeElement.querySelector(...), click them, then assert on text. Test what users see, not private fields.
For HTTP services: provideHttpClient() + provideHttpClientTesting(), then HttpTestingController.expectOne(url) checks the request and flush(data) returns a fake response.
Swap dependencies with providers: { provide: CartService, useValue: fakeCart } — the payoff of using DI everywhere.
Syntax#
await TestBed.configureTestingModule({ imports: [Counter] }).compileComponents();
const fixture = TestBed.createComponent(Counter);
fixture.detectChanges();
fixture.nativeElement.querySelector('button').click();
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('1');
Code example: Specs for a component and an HTTP service#
import { Component, Injectable, inject, signal } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { HttpClient, provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
// ── Code under test ──
export type Product = { id: number; name: string };
@Injectable({ providedIn: 'root' })
export class ProductApi {
private http = inject(HttpClient);
list() { return this.http.get<Product[]>('/api/products'); }
}
@Component({
selector: 'app-like',
template: `<button (click)="like()">Like</button><span>{{ likes() }}</span>`,
})
export class LikeButton {
likes = signal(0);
like() { this.likes.update(n => n + 1); }
}
// ── Component test ──
describe('LikeButton', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({ imports: [LikeButton] }).compileComponents();
});
it('counts clicks', () => {
const fixture = TestBed.createComponent(LikeButton);
fixture.detectChanges();
const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
button.click();
button.click();
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('span').textContent).toBe('2');
});
});
// ── HTTP service test ──
describe('ProductApi', () => {
let api: ProductApi;
let http: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({ providers: [provideHttpClient(), provideHttpClientTesting()] });
api = TestBed.inject(ProductApi);
http = TestBed.inject(HttpTestingController);
});
afterEach(() => http.verify()); // no unexpected requests
it('GETs /api/products', () => {
let result: Product[] = [];
api.list().subscribe(r => (result = r));
const req = http.expectOne('/api/products');
expect(req.request.method).toBe('GET');
req.flush([{ id: 1, name: 'Pen' }]);
expect(result.length).toBe(1);
expect(result[0].name).toBe('Pen');
});
});
ng test reports 2 specs, 0 failures: “LikeButton counts clicks” and “ProductApi GETs /api/products”. No real server is contacted — HttpTestingController answers the request.
This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- TestBed imports the standalone component and creates it with a fixture.
- Clicking the real button and calling detectChanges updates the DOM; the test checks the visible text.
- provideHttpClientTesting replaces the real backend with a controllable fake.
- expectOne asserts that exactly one matching request was made; flush sends the fake response.
- http.verify() fails the test if any request was not handled.
Real-world use case#
A team requires a spec for every service and every component with logic. CI runs ng test --watch=false on each pull request, catching broken bindings and API contract changes early.
Common mistakes#
Without fixture.detectChanges() the template is not rendered or updated, and assertions see old values.
Tests become slow and flaky. Use HttpTestingController or mocks.
Asserting on private fields breaks with refactors. Assert on DOM output and public behaviour.
Best practices#
- Test what users see and services’ public APIs
- Mock HTTP with HttpTestingController
- Swap dependencies through providers
- Run tests in CI on every change
Remember this#
TestBed.configureTestingModule → createComponent → detectChanges.
HTTP: provideHttpClientTesting + expectOne + flush.
DI makes swapping dependencies trivial.
Practice exercise#
Your task: Write a spec that provides a fake GreetingService (useValue with greet() returning "Hi test") to a component that displays service.greet(), and checks the text.
Need a hint?
Show solution
import { Component, Injectable, inject } from '@angular/core';
import { TestBed } from '@angular/core/testing';
@Injectable({ providedIn: 'root' })
export class GreetingService {
greet() { return 'Hello from the real service'; }
}
@Component({ selector: 'app-greeting', template: `<p>{{ service.greet() }}</p>` })
export class Greeting {
protected service = inject(GreetingService);
}
describe('Greeting', () => {
it('uses the injected service', async () => {
await TestBed.configureTestingModule({
imports: [Greeting],
providers: [{ provide: GreetingService, useValue: { greet: () => 'Hi test' } }],
}).compileComponents();
const fixture = TestBed.createComponent(Greeting);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Hi test');
});
});
Mini quiz#
Which call updates the template in a component test?
Want more? Practise all Angular MCQs · Angular interview questions
My notes#
Saved in this browser.