Angular services are usually simpler to test than components, since there's typically no template or DOM rendering involved — just dependency injection and, often, HTTP interactions to mock. This lesson covers both.
Testing a Simple Service
For a service with no external dependencies, TestBed.inject() resolves an instance from the testing module exactly as Angular's real dependency injector would, letting you call its methods and assert on the results directly.
import { TestBed } from '@angular/core/testing';
import { CalculatorService } from './calculator.service';
describe('CalculatorService', () => {
let service: CalculatorService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(CalculatorService);
});
it('adds two numbers', () => {
expect(service.add(2, 3)).toBe(5);
});
});
No declarations or providers are needed here since CalculatorService has no dependencies of its own to resolve.
Mocking HTTP with HttpClientTestingModule
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { UserService } from './user.service';
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();
});
it('fetches users from the API', () => {
let result: any[] = [];
service.getUsers().subscribe((users) => (result = users));
const req = httpMock.expectOne('/api/users');
req.flush([{ id: 1, name: 'Ada' }]);
expect(result).toEqual([{ id: 1, name: 'Ada' }]);
});
});
HttpClientTestingModule replaces Angular's real HttpClient backend with a controllable mock.
HttpTestingController.expectOne() asserts exactly one matching request was made, and returns it for you to control.
req.flush(data) simulates a successful response with the given body.
httpMock.verify() in afterEach ensures no unexpected/unhandled requests were left outstanding.
Service Testing Cheat Sheet
Key APIs for testing services with and without HTTP dependencies.
HttpTestingController can simulate failed requests just as easily as successful ones, which is essential for verifying a service's error-handling logic actually works as intended.
For services depending on other services (rather than HTTP directly), the same providers + spy object pattern from component testing applies identically here.
Forgetting httpMock.verify() in afterEach, silently missing unexpected or duplicate HTTP requests.
Testing against a real backend instead of HttpClientTestingModule, making tests slow and network-dependent.
Not testing error-handling paths at all, only ever simulating successful responses.
Providing a real, heavy dependency service when a lightweight spy object would isolate the test better.
Key Takeaways
TestBed.inject() resolves a service instance exactly as Angular's real injector would.
HttpClientTestingModule and HttpTestingController provide full control over mocked HTTP requests.
req.flush() simulates both successful and error responses for thorough error-handling coverage.
httpMock.verify() catches unexpected outstanding requests a test might otherwise miss silently.
Pro Tip
Always write at least one error-path test alongside every happy-path HTTP service test. HttpTestingController.flush() makes simulating a 500 or 404 nearly as easy as a success response, so there's little excuse to skip it.
You now understand Angular service testing with Karma. Next, learn how to test pipes and directives specifically.