Testing
TestBed, ComponentFixture, HttpClientTestingModule, E2E with Playwright
Angular Testing is supported by TestBed — a test module that creates an Angular environment in Jasmine/Jest. Knowing when to use a full TestBed vs a lightweight unit test, how to test components with the DOM, and how to mock HTTP calls is the difference between a fast, maintainable test suite and a slow, brittle one.
Key Points
- TestBed.configureTestingModule(): declares components, imports modules, provides mock services — creates a mini-Angular app
- ComponentFixture<T>: wrapper around a component instance — fixture.componentInstance, fixture.nativeElement, fixture.debugElement
- fixture.detectChanges(): triggers Angular's change detection — call after setting @Input or changing signals
- By.css('.my-class'): DebugElement query — returns Angular-aware element wrappers with component instances
- HttpClientTestingModule + HttpTestingController: intercept HTTP calls in tests — no real network, flush() to return mock data
- Spectator: wrapper library that reduces TestBed boilerplate — createComponent(), byLabel(), detectChanges()
- Jest (via jest-preset-angular): faster than Karma, better snapshot testing, modern test runner
- Playwright / Cypress: E2E testing — test the full app in a real browser; component testing now supported in both
- inject() in TestBed: TestBed.inject(Service) to get instances of services from the test injector
Angular testing: TestBed with SpyObj, fakeAsync + tick for debounce, HttpClientTestingModule, HttpTestingController
// Component unit test
describe('SearchComponent', () => {
let component: SearchComponent;
let fixture: ComponentFixture<SearchComponent>;
let searchService: jasmine.SpyObj<SearchService>;
beforeEach(async () => {
searchService = jasmine.createSpyObj('SearchService', ['find']);
searchService.find.and.returnValue(of([{ id: 1, name: 'Widget' }]));
await TestBed.configureTestingModule({
imports: [SearchComponent], // standalone component
providers: [{ provide: SearchService, useValue: searchService }]
}).compileComponents();
fixture = TestBed.createComponent(SearchComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('displays results after typing', fakeAsync(() => {
const input = fixture.debugElement.query(By.css('input'));
input.nativeElement.value = 'widget';
input.nativeElement.dispatchEvent(new Event('input'));
tick(300); // advance debounceTime
fixture.detectChanges();
const items = fixture.debugElement.queryAll(By.css('.result-item'));
expect(items.length).toBe(1);
expect(items[0].nativeElement.textContent).toContain('Widget');
}));
});
// HTTP testing
describe('UserService', () => {
let service: UserService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [UserService]
});
service = TestBed.inject(UserService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => httpMock.verify()); // assert no outstanding requests
it('fetches users', () => {
service.getUsers().subscribe(users => expect(users).toHaveSize(2));
const req = httpMock.expectOne('/api/users');
expect(req.request.method).toBe('GET');
req.flush([{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]);
});
});Real-World Example
fakeAsync() + tick(ms) is how you test debounce, delay, and timer-based code without actually waiting. It controls Angular's fake async zone clock. Without it, you'd need done() callbacks and real setTimeout delays. Most Angular testing slowness comes from loading full @SpringBootTest-equivalent TestBed setups for every test — use spyObj mocks aggressively and reserve full TestBed for integration scenarios.