Tests keep large Angular apps maintainable; TestBed is the standard way to test with real DI and templates.
New Angular projects use Vitest as the unit test runner (older ones use Karma and Jasmine; the APIs below are the same). Run tests with ng test.
TestBed creates a testing module with Angular's DI and compiler:
- Services:
TestBed.inject(MyService)afterTestBed.configureTestingModule({ providers: [...] }). Replace dependencies with fakes using{ provide: Real, useValue: fake }. - Components:
const fixture = TestBed.createComponent(MyComponent);fixture.componentInstanceis the class,fixture.nativeElementis the DOM. - Set inputs with
fixture.componentRef.setInput('name', 'Ada'). - In zoneless apps, `await fixture.whenStable()` waits for change detection and pending work after you change state or trigger events;
fixture.detectChanges()forces a synchronous pass.
For HTTP, provide provideHttpClient() and provideHttpClientTesting(), then use `HttpTestingController`: httpMock.expectOne('/api/todos').flush([...]) returns fake data, and httpMock.verify() checks there were no unexpected requests.
Test behaviour as a user sees it: query the DOM, click buttons (button.click()), assert rendered text. Component harnesses (from the CDK) give a stable API for interacting with complex components like Angular Material's.
Pure logic (pipes, utility functions, computed-heavy services) can be tested as plain classes without TestBed. For end-to-end tests, use Playwright or Cypress.
Example
import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { UserDetail } from './user-detail'; // the httpResource example component
describe('UserDetail', () => {
beforeEach(() => {
TestBed.configureTestingModule({ providers: [provideHttpClient(), provideHttpClientTesting()] });
});
it('renders the loaded user', async () => {
const fixture = TestBed.createComponent(UserDetail);
const http = TestBed.inject(HttpTestingController);
await fixture.whenStable();
http.expectOne('https://jsonplaceholder.typicode.com/users/1')
.flush({ id: 1, name: 'Ada', email: 'ada@example.com' });
await fixture.whenStable();
const el: HTMLElement = fixture.nativeElement;
expect(el.querySelector('h2')?.textContent).toContain('Ada');
http.verify(); // no unexpected requests
});
});Remember
TestBed.configureTestingModule+createComponent/injectfor tests with real DI- Use
setInputfor inputs andawait fixture.whenStable()in zoneless apps provideHttpClientTesting+HttpTestingControllerfake HTTP responses
Watch out for
- Asserting on the DOM right after changing state without awaiting
whenStable()or callingdetectChanges().