Isolating Dependencies in Jasmine: Using Spies to Avoid Real Network Calls
Learn how to use Jasmine spies to isolate units under test, mock HTTP dependencies, and verify function calls without relying on live network requests.
06 Apr 2026, 03:44 UTC

The Problem: Testing Logic Without the Network
When testing a service that fetches data from an API, you face a dilemma: if you make real HTTP requests, your tests become slow, flaky, and dependent on a live server. If you don't, you can't verify that your service is sending the correct parameters to the API or handling the response correctly.
The solution is to use spies. In Jasmine, a spy is a test double that replaces a real function. It lets you intercept calls, return fake data instantly, and verify exactly how the function was invoked without ever leaving your local environment.
Choosing Your Spy Strategy
Depending on how your code is structured, you will use one of two primary methods to create a double.
1. spyOn(object, 'methodName')
Use spyOn when you have an existing object (like a shared HTTP client or a service instance) and you want to replace a specific method on that object. This is the most common approach for mocking dependencies that are already instantiated. Note that the property must exist and be configurable on the target object, or Jasmine will throw an error.
2. jasmine.createSpy('spyName')
Use createSpy when you need a standalone function that doesn't belong to an object. This is ideal for dependency injection, where you pass a fake callback or mock function into a constructor or method argument, keeping your test doubles explicit rather than global.
Controlling the Outcome
A spy by default does nothing: it records the call but returns undefined. To make your tests useful, you must define a call strategy to simulate different API scenarios:
- and.returnValue(value): Returns a synchronous value.
- and.resolveTo(value): Returns a Promise that resolves to the value (essential for
async/awaitpatterns). - and.rejectWith(error): Returns a Promise that rejects, allowing you to test your
catchblocks. - and.callFake(fn): Executes a custom function, useful for logic that depends on the input arguments.
- and.callThrough(): Records the call but still executes the original implementation.
Spy strategy names have evolved across Jasmine major versions, so confirm the syntax against the version installed in your project (check npm ls jasmine).
Worked Example: Mocking an HTTP Client
Assume we have a UserService that depends on an httpClient to fetch user profiles. We want to ensure the service correctly transforms the raw API response into a display value.
// The service under test
class UserService {
constructor(httpClient) { this.httpClient = httpClient; }
async getUserName(id) {
const response = await this.httpClient.get(`/users/${id}`);
return response.fullName.toUpperCase();
}
}
// The spec
describe('UserService', () => {
it('should fetch user and return uppercase name', async () => {
// 1. Setup: create a minimal httpClient object
const mockHttpClient = { get: () => {} };
const service = new UserService(mockHttpClient);
// 2. Spy: intercept 'get' and resolve a fake payload
const fakeUser = { fullName: 'Jane Doe' };
spyOn(mockHttpClient, 'get').and.resolveTo(fakeUser);
// 3. Execute
const result = await service.getUserName(123);
// 4. Assert: transformation and call parameters
expect(result).toBe('JANE DOE');
expect(mockHttpClient.get).toHaveBeenCalledWith('/users/123');
expect(mockHttpClient.get).toHaveBeenCalledTimes(1);
});
});Execution Details
- Where to run: In your project's Jasmine test runner (for example,
npm test), with standard user permissions. - Placeholders: Replace the
/users/${id}endpoint and response shape with your actual API contract. - Risk: The spec must use
async/await; otherwise the test may finish before the Promise resolves, producing a false pass.
Limitations and Trade-offs
Spies introduce the risk of implementation coupling. If you write too many assertions about how a method was called (for example, checking the exact order of five internal calls), your tests will break every time you refactor, even when the final behavior is unchanged.
Practical tip: Focus assertions on the outcome (the returned value) and the critical boundaries (the specific URL sent to the API). Avoid spying on private helpers or asserting on incidental internal call sequences.
Verifying Your Spies
To confirm your spy is working as intended, check the following:
- The test completes without real network activity (no timeouts or connection errors in the console).
- The
toHaveBeenCalledWithmatcher fails if you deliberately change the ID passed to the service — a quick way to prove the assertion is real. - An
and.rejectWithvariant of the spec successfully triggers your error-handling path.
Jasmine automatically removes spies after each it block, so tests stay isolated without manual teardown — a key advantage over hand-rolled monkey-patching.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.