3 min read
How to Properly Spy on Module Functions in your tests

How to use spyOn effectively

Spying on functions while testing allow us to verify that methods or components behave as expected. When spying, we can intercept and check the execution of functions. This is really useful for testing a module without affecting other named exports.

While mocking replaces the function you want to test with a new one (or a fake one), spying maintain the original function in order to observe their behavior.

Spying named exports

You have to spy the file containing the function, and then indicate which of the methods that are part of the file to spy on. Example:

function sum(a, b) {
  return a + b;
}

export { sum }
import * as sumModule from './sum';

test('sum function has received 1 + 2', () => {
  const spy = jest.spyOn(sumModule, 'sum');

  sumModule.sum(1, 2);
  expect(spy).toHaveBeenCalledWith(1, 2);

  spy.mockRestore();
});

Why import * as?

Named exports must be spied on as properties of the module object, not as destructured values. For instance, if you do

import { sum } from './sum';

you get a copy of the function, but not the reference of the module’s property. Because of that, jest.spyOn() won’t work!

Spying default exports

Using the same example as before (but using a default export for the function)

export default function sum(a, b) {
  return a + b;
}

we have to import still the whole module as an object, but now, spy on the default property.

import * as sumModule from './sum';

test('sum function has received 1 + 2', () => {
  const spy = jest.spyOn(sumModule, 'default');

  sumModule.default(1, 2);
  expect(spy).toHaveBeenCalledWith(1, 2);

  spy.mockRestore();
});

The default export is available as sumModule.default, not as sumModule.sum!

Note:
Always call spy.mockRestore() after your test to clean up and avoid side effects in other tests.