Services are the brain of your application. Testing them in isolation ensures that your business logic is correct without the noise of UI or network issues.
1The Power of Spies
A 'Spy' is a double agent. It replaces a real dependency with a controlled version that you can monitor. Using jasmine.createSpyObj(), you can verify not only that a method was called, but how many times it was called and with what exact arguments. This allows you to test that your component is correctly interacting with its services without actually triggering the side effects of those services, such as saving to a database or navigating the router.
2Mocking the Network
You should never make real API calls in a unit test. Real networks are slow and unpredictable. Angular's HttpClientTestingModule provides a 'mock' backend. You can use the HttpTestingController to expect a specific URL, verify the request method (GET, POST), and then 'flush' it with a mock JSON response. This makes your tests synchronous and extremely fast, ensuring that you can verify your data-handling logic in milliseconds.
3Step-by-Step Breakdown
Services handle your data and logic. To test them properly, we must isolate them from the internet and other services.
We use 'Spies' to create fake versions of our dependencies. This ensures that we are testing the service itself, not its neighbors.
For HTTP services, Angular provides the 'HttpClientTestingModule'. It intercepts real requests and lets us provide mock data.
Checkpoint: When testing a service that performs network requests, which module should you import to prevent real HTTP calls?
- →HttpClientModule
- →HttpClientTestingModule
We use 'HttpTestingController' to verify the request and 'flush' it with our fake data to see how the service handles the response.
By mocking every dependency, your tests become deterministic, fast, and completely independent of external systems.
Checkpoint: What is the Jasmine function used to create a mock object with specific methods that you can track?
- →createFake()
- →createSpyObj()
Logic verified! Your services are now bulletproof, regardless of whether the backend is ready or not.
Finally, we'll learn how to take our tested code and deploy it to the world.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Well-Tested Business Logic Frees Up Attention for Accessibility Review Elsewhere
When a service's data transformation and business rules are thoroughly covered by fast unit tests, code review time and manual QA effort can shift toward the DOM/template layer where accessibility actually lives, rather than being consumed re-verifying logic correctness.
2Services Themselves Have No Direct Accessibility Surface
A service class has no DOM and no template — accessibility testing belongs at the component level where markup is rendered; service tests should focus purely on correctness of data and logic, not accessibility concerns that don't apply to this layer.
SEO Implications
- 1
Service Tests Have No Direct SEO Effect but Protect Data Correctness Feeding Indexable Pages
If a service's business logic (formatting, calculations, data shaping) feeds content that ultimately renders as indexable page content, thorough service-level tests reduce the risk of a logic bug silently corrupting that content in a way a crawler would capture.
- 2
Testing HTTP-Calling Services With Mocked Responses Doesn't Verify SSR-Specific Timing
`HttpTestingController`-based tests verify a service's request/response handling logic in isolation, but don't test the separate concern of whether that request actually resolves during Angular Universal's server-render pass — that's a distinct, SSR-specific testing concern.
Best Practices
Use `HttpTestingController` to Mock HTTP Calls in Service Tests
Rather than letting tests hit a real (or even a fake) network endpoint, `HttpTestingController` lets you assert exactly which requests were made and flush controlled mock responses, keeping tests fast, deterministic, and independent of any real backend's availability.
Test a Service's Public API, Not Its Private Internal Methods
Testing through the same methods a real consumer (a component) would call keeps tests focused on the service's actual contract — reaching into private methods for testing convenience couples tests to implementation details likely to change during refactoring.
Frequent Bugs
A service test that calls `HttpClient` hangs indefinitely or times out.
The test never called `httpTestingController.flush(mockData)` to resolve the mocked request — `HttpTestingController` intercepts requests but leaves them pending until the test explicitly flushes a response, unlike a real backend which responds automatically.
`httpTestingController.verify()` throws an error at the end of a test claiming there are outstanding requests.
The service made an HTTP call during the test that was never matched and flushed by the test code — every expected request must be explicitly asserted on (via `expectOne` or similar) and flushed; `verify()` deliberately fails the test if any request was left unhandled, catching untested code paths.
Real-World Examples
Isolated Service Test With Mocked HTTP
A data service's test verifies it makes the correct request and correctly parses a controlled mock response, entirely independent of any real backend availability.
it('fetches user data', () => {
service.getUser(1).subscribe(user => expect(user.name).toBe('Ada'));
const req = httpMock.expectOne('/api/users/1');
req.flush({ name: 'Ada' });
});