How to spy on a default exported function with Jest?

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.

56195 次浏览

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');

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')

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()
})

What worked for me was a combination of the answer from Janne Annala and OP's own solution. All I wanted to test was that the helper method was called with the correct parameters as I had already written a test for the helper method and it didn't have any bearing on my subsequent test:

// myHelperMethod.js


export const myHelperMethod = (param1, param2) => { // do something with the params };
// someOtherFileUsingMyHelperMethod.js


import * as MyHelperMethod from '../myHelperMethod';




jest.mock('../myHelperMethod', () => ({
myHelperMethod: jest.fn(),
}));


let myHelperMethodSpy = jest.spyOn(MyHelperMethod, 'myHelperMethod');


// ...
// some setup
// ...


test(() => {
expect(myHelperMethodSpy).toHaveBeenCalledWith(param1, param2);
});

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!");
})
});

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");

Use 'default' as the second argument in spyOn function.

import * as MyHelperMethod from '../myHelperMethod';


jest.spyOn(MyHelperMethod, 'default');