Skip to content

Jest Spies

A spy watches an existing function without necessarily replacing its behavior. This lesson covers jest.spyOn(), how it differs from jest.fn(), and how to restore the original implementation afterward.

jest.spyOn() vs jest.fn()

jest.fn() creates a mock function from nothing. jest.spyOn(object, 'methodName') instead wraps an *existing* method on a real object, letting the real implementation run by default while still tracking every call — or you can override the behavior just like a regular mock.

This makes spies ideal when you want to observe how your own code calls a method (like console.error or a class method) without necessarily changing what that method actually does.

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

test('spies on a method call without changing behavior', () => {
  const spy = jest.spyOn(calculator, 'add');

  const result = calculator.add(2, 3);

  expect(result).toBe(5);               // real implementation still ran
  expect(spy).toHaveBeenCalledWith(2, 3); // and the call was tracked
});

By default, jest.spyOn() still calls through to the real method, unlike jest.fn(), which starts with no implementation at all.

Overriding and Restoring a Spy

const spy = jest.spyOn(object, 'method');
spy.mockReturnValue(42);       // override the behavior like any mock
spy.mockRestore();             // restore the original implementation
  • By default, a spy calls through to the real method (unless overridden).
  • .mockImplementation()/.mockReturnValue() on a spy override the real method's behavior.
  • .mockRestore() puts the original, un-spied method back in place.
  • restoreMocks: true in Jest config automatically restores all spies after every test.

Jest Spies Cheat Sheet

How spying differs from plain mocking, at a glance.

Action Example
Create a spy jest.spyOn(obj, 'method')
Keep real behavior Default behavior of jest.spyOn()
Override behavior spy.mockReturnValue(x)
Suppress a method entirely jest.spyOn(obj, 'method').mockImplementation(() => {})
Restore original method spy.mockRestore()
Restore all spies automatically restoreMocks: true in config

Spying on console.error or console.log

A very common use of jest.spyOn() is silencing and asserting on console output during a test — for example, confirming a deprecated function logs a warning, without cluttering the actual test output.

test('warns when using a deprecated option', () => {
  const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

  configure({ oldOption: true });

  expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('deprecated'));

  errorSpy.mockRestore();
});

Overriding with .mockImplementation(() => {}) prevents the warning from actually printing during the test run.

Spying on Class Methods

Spies work on class instance methods too, which is useful for verifying that one method calls another internally, without mocking the entire class.

class OrderService {
  createOrder(items) {
    this.validate(items);
    return { items, total: items.length * 10 };
  }
  validate(items) {
    if (!items.length) throw new Error('Empty order');
  }
}

test('createOrder calls validate', () => {
  const service = new OrderService();
  const validateSpy = jest.spyOn(service, 'validate');

  service.createOrder(['item-1']);

  expect(validateSpy).toHaveBeenCalledWith(['item-1']);
});

Common Mistakes

  • Forgetting spies call through to the real implementation by default, causing unexpected side effects.
  • Not calling .mockRestore() (or configuring restoreMocks: true), leaking a spy's override into later tests.
  • Trying to jest.spyOn() a standalone function instead of a method on an object (it requires an object and a key).
  • Overriding a spy with .mockImplementation() when you actually just wanted to observe the real behavior.

Key Takeaways

  • jest.spyOn(obj, 'method') wraps an existing method, calling through to it by default.
  • Spies can be overridden with .mockReturnValue()/.mockImplementation() just like any mock.
  • .mockRestore() returns the original method, undoing the spy's effects.
  • Spies are ideal for observing internal calls (like console.error or a class method) without full mocking.

Pro Tip

Set restoreMocks: true in jest.config.js so every spy is automatically restored after each test — you'll rarely need to remember .mockRestore() manually again.