Mastering Jasmine Spies: Replace, Verify, and Control in Unit Tests
Spies let you replace real functions with mock implementations in Jasmine tests. This guide covers how to create spies, control return values, handle async calls, and avoid common pitfalls.
27 Feb 2026, 22:40 UTC

Why Spies Matter in Jasmine
When unit‑testing JavaScript, you often need to isolate the unit under test from its collaborators. Spies let you replace a real method with a mock implementation while keeping the same signature, so you can assert that the method was called, with what arguments, and how many times. They also let you control what the method returns, which is essential when the real implementation is slow, flaky, or side‑effect‑heavy.
Creating a Spy with spyOn
The core API is spyOn(object, 'methodName'). It mutates the target method, turning it into a spy that records every call. The spy object exposes helpers such as calls.count(), calls.allArgs(), and and.returnValue() to inspect and influence behavior.
const userService = {
getUser: function(id) { /* real implementation */ }
};
describe('UserService', () => {
it('should fetch a user', () => {
spyOn(userService, 'getUser').and.returnValue({ id: 1, name: 'Alice' });
const user = userService.getUser(1);
expect(userService.getUser.calls.count()).toBe(1);
expect(userService.getUser.calls.first().args).toEqual([1]);
expect(user).toEqual({ id: 1, name: 'Alice' });
});
});
Replacing with a Custom Function: and.callFake
Sometimes you need logic that depends on the input. Use and.callFake(fn) to provide a custom implementation while still recording calls.
spyOn(userService, 'getUser').and.callFake(id => {
return { id, name: `User${id}` };
});
Handling Asynchronous Methods
Spies can return promises or call callbacks, enabling you to test async flows. Jasmine will wait for a returned promise if you use async/await or done() callbacks.
spyOn(userService, 'fetchProfile').and.returnValue(
Promise.resolve({ profile: 'active' })
);
it('handles async profile', async () => {
const result = await userService.fetchProfile(1);
expect(result.profile).toBe('active');
});
Trade‑Offs and Common Pitfalls
- Mutation of the target:
spyOnreplaces the original method. If you spy on a shared prototype or a global object, other tests may see the spy unless you restore it withspyOn(...).and.callThrough()orspyOn(...).and.callFake()and manually restore. - Order matters: Creating a second spy on the same method overwrites the first. Plan spy creation early in the
beforeEachblock. - Side‑effect leakage: If the spied method interacts with external state, the spy may mask bugs. Use
and.callThrough()in cases where you want the real implementation to run.
Practical Checklist for Using Spies
- Identify the collaborator you want to isolate.
- Call
spyOnin abeforeEachblock to ensure a fresh spy per test. - Use
and.returnValueorand.callFaketo supply deterministic data. - Assert on
calls.count()andcalls.allArgs()to verify interaction. - For async, return a promise or use
done()to signal completion. - Restore the original method if the spy may affect other tests.
Actionable Closing
Spies are a lightweight way to mock dependencies in Jasmine. Start by replacing the most expensive or flaky collaborators with spyOn(...).and.returnValue(), then expand to callFake for input‑dependent logic. Remember to clean up after each test to avoid cross‑test contamination. With these patterns, your unit tests will become faster, more reliable, and easier to reason about.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.