Using Jasmine Spies to Mock Dependencies in Unit Tests
Learn how Jasmine spies let you mock dependencies, verify calls, and keep tests focused on the unit under test.
14 Aug 2025, 06:14 UTC

When a unit under test reaches out to a database, a network service, or another module, the test becomes slow, flaky, and hard to maintain. Jasmine’s spy mechanism lets you replace those external calls with controllable stubs while still observing how the unit interacts with them.
What Jasmine Spies Provide
A spy wraps an existing function, records every invocation, and lets you dictate its behavior. The core API (Jasmine 4.x) includes:
spyOn(object, 'methodName')– replacesobject.methodNamewith a spy.jasmine.createSpy()– creates a standalone anonymous spy you can pass as a callback.- Modifiers such as
and.callThrough()(record calls but run the original),and.returnValue(value)(stub a return), andand.callFake(fn)(provide a custom implementation). - Expectations like
toHaveBeenCalled(),toHaveBeenCalledWith(arg1, arg2), andtoHaveBeenCalledTimes(n)to verify interactions.
Worked Example: Mocking a Data Service
Consider a presenter that fetches a user profile from a service and updates the view. To make the test deterministic we replace the service with a spy.
// userPresenter.js
class UserPresenter {
constructor(userService, view) {
this._userService = userService;
this._view = view;
}
loadUser(id) {
// Return the promise so the test can await it
return this._userService.getUser(id).then(user => {
this._view.render(user);
});
}
}
The spec below verifies that loadUser calls the service with the correct ID and passes the resolved user to the view, without hitting a real backend.
// userPresenter.spec.js
describe('UserPresenter', () => {
let userService;
let view;
let presenter;
beforeEach(() => {
// Plain objects whose methods we will spy on
userService = { getUser: jasmine.createSpy() };
view = { render: jasmine.createSpy() };
presenter = new UserPresenter(userService, view);
});
it('fetches the user and renders it', async () => {
const fakeUser = { id: 42, name: 'Ada' };
// Stub the service to return a resolved promise with the fake user
userService.getUser.and.returnValue(Promise.resolve(fakeUser));
await presenter.loadUser(42);
// Verify the service received the expected argument
expect(userService.getUser).toHaveBeenCalledWith(42);
// Verify the view received the resolved user
expect(view.render).toHaveBeenCalledWith(fakeUser);
});
});
If the presenter returns the promise (as shown), the spec will pass when the ID is forwarded correctly and the view receives the resolved user. No real network or database calls are made, keeping the test fast.
Trade‑offs and Limitations
- Spies can only replace existing object methods; they cannot directly mock static functions, class constructors, or private members without refactoring the code to expose them.
- Over‑using spies to assert on internal calls (e.g., checking a private helper was invoked) makes tests brittle: a refactor that preserves external behavior but changes internal wiring will break the suite.
Actionable Takeaway
Use Jasmine spies to isolate the unit under test by stubbing its dependencies, verify expected interactions with matchers like toHaveBeenCalledWith, and ensure asynchronous code returns a promise or uses Jasmine’s done callback so the test waits for microtasks. This yields fast, deterministic tests that focus on behavior rather than implementation details.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.