Tests are small programs that check that your code still works after you change it. New Angular projects use Vitest as the default test runner, with the Angular TestBed to create components. Older tutorials use Karma and Jasmine, and the test code looks almost the same.
What you will learn
- How to run tests with
ng test - How to test a component by clicking and checking the text
- How to test an HTTP service without a real server
Run the tests
ng test
This watches your files and re-runs tests when they change. In scripts and on a build server use ng test --watch=false. Test files end in .spec.ts and sit next to the code they check. describe, it and expect are available without imports.
Test the counter component
// src/app/counter/counter.spec.ts
import { TestBed } from '@angular/core/testing';
import { Counter } from './counter';
describe('Counter', () => {
function setup() {
const fixture = TestBed.createComponent(Counter);
const element = fixture.nativeElement as HTMLElement;
return { fixture, element };
}
it('starts at zero', async () => {
const { fixture, element } = setup();
await fixture.whenStable();
expect(element.querySelector('h2')?.textContent).toContain('Counter: 0');
});
it('increases when the button is clicked', async () => {
const { fixture, element } = setup();
const buttons = element.querySelectorAll('button');
buttons[0].click(); // Increase
buttons[0].click();
await fixture.whenStable();
expect(element.querySelector('h2')?.textContent).toContain('Counter: 2');
});
it('resets to zero', async () => {
const { fixture, element } = setup();
const buttons = element.querySelectorAll('button');
buttons[0].click(); // Increase
buttons[2].click(); // Reset
await fixture.whenStable();
expect(element.querySelector('h2')?.textContent).toContain('Counter: 0');
});
});
Each test follows the same pattern, often called arrange, act, assert: create the component, do something the user would do, then check the result. fixture.whenStable() waits until Angular has updated the page.
Test the HTTP service
// src/app/posts/post-store.spec.ts
import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { PostStore } from './post-store';
describe('PostStore', () => {
let store: PostStore;
let http: HttpTestingController;
const url = 'https://jsonplaceholder.typicode.com/posts';
beforeEach(() => {
TestBed.configureTestingModule({
providers: [provideHttpClient(), provideHttpClientTesting()],
});
store = TestBed.inject(PostStore);
http = TestBed.inject(HttpTestingController);
});
afterEach(() => {
http.verify(); // fails if a request was not expected
});
it('loads posts and stops loading', () => {
store.load();
expect(store.loading()).toBe(true);
http.expectOne(url).flush([{ id: 1, userId: 1, title: 'Hello', body: 'World' }]);
expect(store.posts().length).toBe(1);
expect(store.loading()).toBe(false);
expect(store.error()).toBeNull();
});
it('shows a friendly message when the server fails twice', () => {
store.load();
// retry(1) sends the request a second time, so fail both.
http.expectOne(url).flush('error', { status: 500, statusText: 'Server Error' });
http.expectOne(url).flush('error', { status: 500, statusText: 'Server Error' });
expect(store.loading()).toBe(false);
expect(store.error()).toContain('Something went wrong');
});
});
provideHttpClientTesting() replaces the real network. You decide the answer with flush(), so tests are fast and need no internet.
The key parts
- Test behaviour the user sees (text, buttons), not private details of the class.
- Keep tests independent.
beforeEachgives every test a fresh start. http.verify()inafterEachcatches forgotten or unexpected requests.- Test the failure paths too, as the second service test does.
- Name tests as sentences: "increases when the button is clicked".
Try it yourself
- Add a test for the Decrease button.
- Write a test for the
addmethod ofPostStoreusinghttp.expectOne(url)with methodPOST. - Break the counter on purpose and watch the test fail with a helpful message.
Quick check
- Which command runs the tests once and exits?
- What does
provideHttpClientTesting()do? - Why test error cases?
Next, make the app usable for everyone with accessibility. See the testing guide for more.