Jest SpyOn Guide

Jest SpyOn: how jest.spyOn() actually works

Learn how to watch existing methods, inspect calls and arguments, change return values, mock implementations, test async methods and restore spies correctly.

Practical examples Copy-ready Jest code Clear error explanations
user.test.js ✓ Passing
const user = {
  getName() {
    return 'Alex';
  }
};

const spy =
  jest.spyOn(user, 'getName');

user.getName();

expect(spy)
  .toHaveBeenCalledTimes(1);

What is jest.spyOn()?

jest.spyOn() creates a Jest mock around a method that already exists on an object. It lets your test see when that method was called, which arguments were passed and what the call returned.

A spy is different from replacing a function completely. By default, the real method still runs. You only replace its behavior when you add a mock such as mockReturnValue() or mockImplementation().

Basic syntax
const spy = jest.spyOn(object, 'methodName');

Use Jest spies with confidence

These are the parts of jest.spyOn() that matter most when you are writing or debugging a real Jest test.

How does jest.spyOn() work?

A spy must be attached to a method that already exists on an object. Jest wraps that method so it can record what happens when your application calls it.

The object still owns the method. That is why the syntax requires both the object and the name of the method.

The most important spyOn rule

Creating a spy does not stop the original function from running. The original implementation is called unless you explicitly mock it.

Basic spy example
const calculator = {
  add(a, b) {
    return a + b;
  }
};

const spy = jest.spyOn(calculator, 'add');

calculator.add(2, 3);

expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(2, 3);

What does jest.spyOn() record?

After the method runs, Jest stores information about each call. You can then use Jest matchers to verify what happened.

  • Number of times the method was called.
  • Arguments passed to every call.
  • Order of the calls.
  • Returned values.
  • Whether a call returned or threw an error.
Checking calls and arguments
const spy = jest.spyOn(api, 'send');

api.send('first');
api.send('second');

expect(spy).toHaveBeenCalledTimes(2);

expect(spy)
  .toHaveBeenNthCalledWith(1, 'first');

expect(spy)
  .toHaveBeenNthCalledWith(2, 'second');

Mock a return value

Use mockReturnValue() when you want the method to return a predictable value instead of running its real implementation.

mockReturnValue()
const spy = jest
  .spyOn(userService, 'getName')
  .mockReturnValue('Test User');

expect(userService.getName())
  .toBe('Test User');

expect(spy)
  .toHaveBeenCalledTimes(1);

Change behavior with mockImplementation()

A fixed value is not always enough. Use mockImplementation() when the mocked method needs custom logic.

Custom implementation
const spy = jest
  .spyOn(math, 'multiply')
  .mockImplementation((a, b) => {
    return 100;
  });

expect(math.multiply(4, 5))
  .toBe(100);

expect(spy)
  .toHaveBeenCalledWith(4, 5);

Spy on async methods

For Promise-based functions, Jest provides mockResolvedValue() and mockRejectedValue() .

Async spy
const spy = jest
  .spyOn(api, 'fetchUser')
  .mockResolvedValue({
    id: 1,
    name: 'Alex'
  });

await expect(api.fetchUser())
  .resolves
  .toEqual({
    id: 1,
    name: 'Alex'
  });

Restore the original method

If you changed the implementation of a spy, restore it after the test. This prevents mocked behavior from affecting another test.

mockRestore()
const spy = jest
  .spyOn(service, 'getValue')
  .mockReturnValue('mocked');

expect(service.getValue())
  .toBe('mocked');

spy.mockRestore();

Common jest.spyOn() errors

Most spy problems are caused by the target object, module export shape or the point at which the spy was created.

Property does not exist

The method is not available on the object passed to jest.spyOn(), or you imported the module in a different shape.

Cannot redefine property

The property cannot be replaced normally. This commonly appears with some module exports and requires a different mocking approach.

Original method still runs

This is normal behavior for jest.spyOn(). Add a mock implementation when the real method should not execute.

Spy records zero calls

Check that the spy was created before the method was called and that the application is using the same object you spied on.

View all Jest SpyOn errors →

jest.spyOn() vs jest.fn()

Both create Jest mock functions, but they start from different situations.

Feature
jest.spyOn()
jest.fn()
Existing method required
Yes
No
Records calls
Yes
Yes
Real code runs by default
Yes
No
Can restore original method
Yes
Not automatically

Read the full SpyOn vs jest.fn() comparison →

Jest SpyOn questions

Straight answers to the questions developers commonly run into while using Jest spies.

jest.spyOn() watches a method that already exists and creates a Jest mock around it. The mock records calls, arguments and results.

Yes. The original implementation runs by default. Use a mock return value or mock implementation when you want different behavior.

Yes. Matchers such as toHaveBeenCalledWith() and toHaveBeenNthCalledWith() let you check arguments recorded by the spy.

Use mockRestore() when a spy changed a method and you want to return that method to its original implementation.

Neither is always better. Use jest.spyOn() for an existing object method and jest.fn() when you need a new standalone mock function.