How to spy on a default exported function with Jest?

38,877

Solution 1

I ended up ditching the default export:

// UniqueIdGenerator.js
export const uniqueIdGenerator = () => Math.random().toString(36).substring(2, 8);

And then I could use and spy it like this:

import * as UniqueIdGenerator from './UniqueIdGenerator';
// ...
const spy = jest.spyOn(UniqueIdGenerator, 'uniqueIdGenerator');

Some recommend wrapping them in a const object, and exporting that. I suppose you can also use a class for wrapping.

However, if you can't modify the class there's still a (not-so-nice) solution:

import * as UniqueIdGenerator from './UniqueIdGenerator';
// ...
const spy = jest.spyOn(UniqueIdGenerator, 'default');

Solution 2

one could also mock the import and pass the original implementation as mock implementation, like:

import uniqueIdGenerator from './UniqueIdGenerator'; // this import is a mock already

jest.mock('./UniqueIdGenerator.js', () => {
  const original = jest. requireActual('./UniqueIdGenerator')
  return {
     __esModule: true,
     default: jest.fn(original.default)
  }
})

test(() => {
  expect(uniqueIdGenerator).toHaveBeenCalled()
})

Solution 3

In some cases you have to mock the import to be able to spy the default export:

import * as fetch from 'node-fetch'

jest.mock('node-fetch', () => ({
  default: jest.fn(),
}))

jest.spyOn(fetch, 'default')

Solution 4

Mock only the default export, or any other export, but keep remaining exports in module as original:

import myDefault, { myFunc, notMocked } from "./myModule";

jest.mock("./myModule", () => {
  const original = jest.requireActual("./myModule");
  return {
    __esModule: true,
    ...original,
    default: jest.fn(),
    myFunc: jest.fn()
  }
});

describe('my description', () => {
  it('my test', () => {
    myFunc();
    myDefault();
    expect(myFunct).toHaveBeenCalled();
    expect(myDefault).toHaveBeenCalled();
    
    myDefault.mockImplementation(() => 5);
    expect(myDefault()).toBe(5);
    expect(notMocked()).toBe("i'm not mocked!");
  })
});

Solution 5

Here is a way of doing it for a default export without modifying the import (or even needing an import in the test at all):

const actual = jest.requireActual("./UniqueIdGenerator");
const spy = jest.spyOn(actual, "default");
Share:
38,877
thisismydesign
Author by

thisismydesign

Updated on July 05, 2022

Comments

  • thisismydesign
    thisismydesign almost 2 years

    Suppose I have a simple file exporting a default function:

    // UniqueIdGenerator.js
    const uniqueIdGenerator = () => Math.random().toString(36).substring(2, 8);
    
    export default uniqueIdGenerator;
    

    Which I would use like this:

    import uniqueIdGenerator from './UniqueIdGenerator';
    // ...
    uniqueIdGenerator();
    

    I want to assert in my test that this method was called while keeping the original functionality. I'd do that with jest.spyOn however, it requires an object as well as a function name as parameters. How can you do this in a clean way? There's a similar GitHub issue for jasmine for anyone interested.

  • Christopher Francisco
    Christopher Francisco almost 5 years
    but that only works when it's transpiled by babel through es6 modules. Won't work on CommonJS
  • Switch386
    Switch386 about 3 years
    Downvoted because it doesn't address the crux of the original question - how to do this with default exports
  • AdamJB
    AdamJB about 3 years
    Despite the fact that OP themselves opted to ditch the default export in their own accepted answer?
  • Moshe Binieli
    Moshe Binieli almost 3 years
    Beautiful answer, very helpful.
  • pmrotule
    pmrotule almost 3 years
    Very nice! Personally, I still needed to have the default method returning its original value so I called it inside jest.fn() like: default: jest.fn((...args) => original(...args)).
  • Christopher Francisco
    Christopher Francisco over 2 years
    This should be the accepted answer
  • CodingWithSpike
    CodingWithSpike over 2 years
    actually thanks to jest setting resetMocks:true by default now, this only works for the 1st test. After that the default: jest.fn gets reset and becomes undefined, so in modern jest with the default config this no longer works either without another workaround
  • agentp
    agentp over 2 years
    Cleanest answer for me
  • kylejw2
    kylejw2 over 2 years
    jest.mock isn't necessary in this usecase. You could also write jest.spyOn(fetch, 'default').mockImplementation(jest.fn())
  • benmneb
    benmneb about 2 years
    i like const spy = jest.spyOn(UniqueIdGenerator, 'default'); because youdont need to refactor any code and risk something happening...