Spying on the Right Things: Practical Guidance for Jasmine’s Spy Feature
Learn how to use Jasmine’s spyOn and jasmine.createSpy to isolate units, verify interactions, and keep tests close to real interfaces. This guide covers setup, an example, trade‑offs, and how to validate your spies in real projects.
23 Jun 2026, 11:15 UTC

Why Spies Matter in Unit Tests
When you write a unit test, you want to focus on the behavior of the code under test, not on the side‑effects of its collaborators. In JavaScript, the most common way to achieve that isolation in Jasmine is to replace a method with a spy. Spies let you record every call, inspect arguments, and optionally replace the implementation with a simple stub or a custom function.
The central problem many developers face is that real collaborators—network clients, DOM APIs, or third‑party services—introduce flakiness and slow down the test suite. Spies provide a lightweight, framework‑native solution that keeps your tests fast and deterministic.
Core Spy APIs and Their Use Cases
1. Replacing an Existing Method – spyOn(obj, 'method')
Use spyOn when you want to monitor a method that already exists on an object. The original implementation is hidden, but you can choose whether to keep it or replace it.
// Assume we have an API client with a method called fetchData
const apiClient = {
fetchData: function(url) { /* real network call */ }
};
// In the spec
beforeEach(() => {
spyOn(apiClient, 'fetchData').and.returnValue(Promise.resolve({data: 42}));
});
it('should call fetchData with the correct URL', () => {
myService.load(apiClient, 'https://example.com');
expect(apiClient.fetchData).toHaveBeenCalledWith('https://example.com');
});
Here and.returnValue tells the spy to return a resolved promise, avoiding an actual HTTP request.
2. Creating a Stand‑Alone Spy – jasmine.createSpy()
When the collaborator is a callback or an event handler, you can pass a standalone spy directly, which is handy for asynchronous flows.
const onSuccess = jasmine.createSpy('onSuccess');
myAsyncFunction(onSuccess);
expect(onSuccess).toHaveBeenCalled();
3. Configuring Spy Behavior
Spies expose several configuration methods:
and.returnValue(value)– return a static value.and.callFake(fn)– delegate to a custom function.and.throwError(error)– simulate an exception.and.callThrough()– let the original method run while still recording the call.
Choosing the right configuration keeps the test focused on the interaction contract rather than the implementation details.
Inspecting Spy Calls in Depth
Beyond the built‑in matchers, Jasmine exposes the calls object for richer assertions:
expect(apiClient.fetchData.calls.count()).toBe(1);
const firstCallArgs = apiClient.fetchData.calls.argsFor(0);
expect(firstCallArgs[0]).toBe('https://example.com');
When the arguments are complex objects or functions, calls.argsFor gives you a snapshot you can compare or deep‑clone, avoiding fragile string comparisons.
Trade‑Offs and Limitations
- Over‑Mocking with
and.callFake: Re‑implementing a collaborator’s logic can make the test mirror the implementation rather than the contract. Useand.returnValueorand.callThroughwhen the real behavior matters. - Property Existence:
spyOnrequires the property to exist and be configurable at call time. Spying on a non‑existent or non‑configurable property throws an error that varies between Jasmine 2.x and 5.x. Verify the property exists before spying. - Version Compatibility: The spy API syntax changed slightly across major releases. Check
jasmine -vor thepackage.jsonversion to confirm you’re using the correct syntax.
How to Verify Your Spy is Working
- Run the spec and ensure it passes with the expected assertion.
- Intentionally change the expected argument in
toHaveBeenCalledWithand confirm the test fails with a readable diff. - Inspect
spy.calls.count()andspy.calls.argsFor(0)in a passing test to confirm the spy recorded the call. - Check the installed Jasmine version:
npm list jasmineoryarn list jasmineto match the API you’re using.
Actionable Takeaway
Use spyOn for real collaborator objects to keep your tests close to production code, and jasmine.createSpy for callbacks or event handlers. Configure spies with and.returnValue or and.callThrough to balance isolation with realism. Validate your spies by inspecting calls and intentionally breaking assertions to confirm they record interactions. Finally, stay aware of version differences and avoid over‑mocking with and.callFake unless you’re certain the implementation is part of the contract you’re testing.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.