99

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.

thisismydesign
  • 21,553
  • 9
  • 123
  • 126

10 Answers10

102

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');
thisismydesign
  • 21,553
  • 9
  • 123
  • 126
  • 10
    but that only works when it's transpiled by babel through es6 modules. Won't work on CommonJS – Christopher Francisco Aug 27 '19 at 19:49
  • 4
    i like `const spy = jest.spyOn(UniqueIdGenerator, 'default');` because youdont need to refactor any code and risk something happening... – benmneb Jun 29 '22 at 05:36
  • 2
    This does not work for me with modules installed in node_modules. I tried second solution like this: `import * as fetchModule from 'cross-fetch'`. Then spy with `jest.spyOn(fetchmodule, 'default')`. I am getting error message: `Cannot assign to read only property 'default' of object '[object Object]'` – Michael S Jan 22 '23 at 15:16
  • 1
    Thank you. Typescript/Nodejs/Jest is just a pain. – Srihari GouthamGr Aug 08 '23 at 08:41
29

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");
Hubris
  • 1,842
  • 2
  • 18
  • 26
27

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()
})
alex.spri
  • 379
  • 3
  • 3
  • This should be the accepted answer – Christopher Francisco Oct 21 '21 at 14:44
  • 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 – CodingWithSpike Nov 09 '21 at 16:24
  • This one works for me also with packages in node_modules. With each test you can define mock function with `uniqueIdGenerator.mockImplementation(...)` – Michael S Jan 22 '23 at 15:23
13

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')
Janne Annala
  • 25,928
  • 8
  • 31
  • 41
  • 3
    `jest.mock` isn't necessary in this usecase. You could also write `jest.spyOn(fetch, 'default').mockImplementation(jest.fn())` – kylejw2 Mar 14 '22 at 19:42
  • 3
    @kylejw2, I am getting `Cannot assign to read only property 'default' of object '[object Object]'` when I do this. – Michael S Jan 22 '23 at 15:19
12

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

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

jest.spyOn(MyHelperMethod, 'default');
joemaller
  • 19,579
  • 7
  • 67
  • 84
9

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!");
  })
});
deckele
  • 4,623
  • 1
  • 19
  • 25
4

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);
});
AdamJB
  • 432
  • 7
  • 10
  • 1
    Downvoted because it doesn't address the crux of the original question - how to do this with default exports – Switch386 Jun 03 '21 at 16:11
  • 2
    Despite the fact that OP themselves opted to ditch the default export in their own accepted answer? – AdamJB Jun 05 '21 at 08:34
2

I know I'm late to the party but I recently had this problem and wanted to share my solution as well ... though it seems a bit more unconventional but could be tweaked by someone with better knowledge.

I happen to have a file with the function that I would like to spy on.

// /foo/ModuleToBeMocked.ts
const fnToSpyOn = () => ...;

export default { fnToSpyOn }

This is then imported into a parent file that would bring, and export, alike functions. Sort of like a classification.

// /parent.ts
import fnToSpyOn from './foo/ModuleToBeMocked';
import someOtherFn from './foo/SomeOtherModule';
...

export { fnToSpyOn, someOtherFn, ... };

And this is how I test the fnToSpyOn

// /foo/ModuleToBeMocked.test.ts
import { ModuleToBeMocked } from '../parent';

const fnToSpyOnSpu = jest.spyOn(ModuleToBeMocked, 'fnToSpyOn');
Mario
  • 339
  • 4
  • 17
1

Here it is even simpler.

Mock your exported module 'addDelay' (has the sleep function in it) using jest.

const { sleep } = require('../../src/utils/addDelay');

jest.mock('../../src/utils/addDelay', () => {
const delay = jest.requireActual('../../src/utils/addDelay');
return {
  ...delay,
  sleep: jest.fn(),
};});

And the test is as follows and check if sleep function was called with 1 sec as in arg.

test("Should delay 1 second if Okta user has no IDxM Roles", async () => {
    // GIVEN
    const MockSleep = sleep;

    // WHEN
    await getUser(req, res);
    
    // THEN
    expect(MockSleep).toHaveBeenCalledWith(1000);// sleep(1000): 1sec
});
Ram
  • 3,887
  • 4
  • 27
  • 49
1

The solution by the user thisismydesign(the first answer) didn't work for me. I made a few modifications to get to work.

First, I exported a default object:

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

To use it, I did it as follows:

import generator from './UniqueIdGenerator';
// ...
const spy = jest.spyOn(generator, 'uniqueIdGenerator');
Stanley Ulili
  • 702
  • 5
  • 7