thisismydesign
thisismydesign

Reputation: 25172

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.

Upvotes: 109

Views: 92373

Answers (10)

alex.spri
alex.spri

Reputation: 429

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

Upvotes: 32

Stanley Ulili
Stanley Ulili

Reputation: 1424

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

Upvotes: 0

Mario
Mario

Reputation: 369

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

Upvotes: 1

kapishreshth
kapishreshth

Reputation: 4098

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

Upvotes: 1

Alexander Ivannikov
Alexander Ivannikov

Reputation: 151

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

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

jest.spyOn(MyHelperMethod, 'default');

Upvotes: 15

Hubris
Hubris

Reputation: 2022

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

Upvotes: 45

deckele
deckele

Reputation: 4893

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

Upvotes: 11

AdamJB
AdamJB

Reputation: 452

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

Upvotes: 5

Janne Annala
Janne Annala

Reputation: 28776

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

Upvotes: 14

thisismydesign
thisismydesign

Reputation: 25172

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

Upvotes: 113

Related Questions