492

I want to test that one of my ES6 modules calls another ES6 module in a particular way. With Jasmine this is super easy --

The application code:

// myModule.js
import dependency from './dependency';

export default (x) => {
  dependency.doSomething(x * 2);
}

And the test code:

//myModule-test.js
import myModule from '../myModule';
import dependency from '../dependency';

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    spyOn(dependency, 'doSomething');

    myModule(2);

    expect(dependency.doSomething).toHaveBeenCalledWith(4);
  });
});

What's the equivalent with Jest? I feel like this is such a simple thing to want to do, but I've been tearing my hair out trying to figure it out.

The closest I've come is by replacing the imports with requires, and moving them inside the tests/functions. Neither of which are things I want to do.

// myModule.js
export default (x) => {
  const dependency = require('./dependency'); // Yuck
  dependency.doSomething(x * 2);
}

//myModule-test.js
describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    jest.mock('../dependency');

    myModule(2);

    const dependency = require('../dependency'); // Also yuck
    expect(dependency.doSomething).toBeCalledWith(4);
  });
});

For bonus points, I'd love to make the whole thing work when the function inside dependency.js is a default export. However, I know that spying on default exports doesn't work in Jasmine (or at least I could never get it to work), so I'm not holding out hope that it's possible in Jest either.

4
  • I'm using Babel for this project anyway, so I don't mind continuing to transpile imports to requires for now. Thanks for the heads up though. Commented Nov 7, 2016 at 21:54
  • what if i have ts class A and it calls some function lets say doSomething() of class B how can we mock so that class A makes call to mocked version of class B function doSomething() Commented Nov 3, 2017 at 9:41
  • 1
    for those who want to discover this issue more github.com/facebook/jest/issues/936
    – omeralper
    Commented Feb 12, 2019 at 0:18
  • 1
    As of 2023 the way to go is the answer from @cdauth, it works. However, I found out that using Vitest will work out-of-the-box, so I will recommend just using Vitest in case you need to cope with ECMAScript Modules. Check more here: vitest.dev/guide/mocking.html#modules
    – hfc
    Commented Jul 19, 2023 at 9:36

10 Answers 10

301

Edit: Several years have passed and this isn't really the right way to do this any more (and probably never was, my bad).

Mutating an imported module is nasty and can lead to side effects like tests that pass or fail depending on execution order.

I'm leaving this answer in its original form for historical purposes, but you should really use jest.spyOn or jest.mock. Refer to the jest docs or the other answers on this page for details.

Original answer follows:


I've been able to solve this by using a hack involving import *. It even works for both named and default exports!

For a named export:

// dependency.js
export const doSomething = (y) => console.log(y)
// myModule.js
import { doSomething } from './dependency';

export default (x) => {
  doSomething(x * 2);
}
// myModule-test.js
import myModule from '../myModule';
import * as dependency from '../dependency';

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    dependency.doSomething = jest.fn(); // Mutate the named export

    myModule(2);

    expect(dependency.doSomething).toBeCalledWith(4);
  });
});

Or for a default export:

// dependency.js
export default (y) => console.log(y)
// myModule.js
import dependency from './dependency'; // Note lack of curlies

export default (x) => {
  dependency(x * 2);
}
// myModule-test.js
import myModule from '../myModule';
import * as dependency from '../dependency';

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    dependency.default = jest.fn(); // Mutate the default export

    myModule(2);

    expect(dependency.default).toBeCalledWith(4); // Assert against the default
  });
});

14
  • 87
    This works, but it's probably not a good practice. Changes to objects outside the scope of the test seem to be persisted between tests. This can later on lead to unexpected results in other tests. Commented May 23, 2017 at 13:58
  • 20
    Instead of using jest.fn(), you could use jest.spyOn() so you can restore the original method later, so it does not bleed into other tests. I found nice article about different approaches here (jest.fn, jest.mock and jest.spyOn): medium.com/@rickhanlonii/understanding-jest-mocks-f0046c68e53c .
    – Martinsos
    Commented Jul 5, 2018 at 13:29
  • 4
    Just a note: if the dependency is reside on the same file as myModule, it will not work.
    – Lu Tran
    Commented Nov 15, 2018 at 19:04
  • 35
    I think this won't work with Typescript the object you're mutating is read-only.
    – anlogg
    Commented May 5, 2020 at 15:11
  • 2
    This doesn't work with the node experimental modules turned on in package.json with type: module. I got it to work with the babel transpiler. Commented Nov 22, 2020 at 15:46
286

You have to mock the module and set the spy by yourself:

import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency', () => ({
  doSomething: jest.fn()
}))

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    myModule(2);
    expect(dependency.doSomething).toBeCalledWith(4);
  });
});
4
  • Thanks, but I still can't get it to work with that approach. jest.mock just doesn't seem to be able to mutate the module well enough for other imports to be effected. I'm about to post an answer with another solution, which is kinda close enough to what I'm after. Commented Nov 8, 2016 at 11:35
  • 6
    @IrisSchaffer in order to have this work with the default export you need to add __esModule: true to the mock object. That's the internal flag used by the transpiled code to determine whether it's a transpiled es6 module or a commonjs module. Commented May 3, 2017 at 20:53
  • 44
    Mocking default exports: jest.mock('../dependency', () => ({ default: jest.fn() }))
    – Neob91
    Commented Jun 13, 2017 at 15:19
  • Didn't work for me: The module factory of jest.mock() is not allowed to reference any out-of-scope variables. Commented Sep 30, 2022 at 10:27
211

Fast forwarding to 2020, I found this blog post to be the solution: Jest mock default and named export

Using only ES6 module syntax:

// esModule.js
export default 'defaultExport';
export const namedExport = () => {};

// esModule.test.js
jest.mock('./esModule', () => ({
  __esModule: true, // this property makes it work
  default: 'mockedDefaultExport',
  namedExport: jest.fn(),
}));

import defaultExport, { namedExport } from './esModule';
defaultExport; // 'mockedDefaultExport'
namedExport; // mock function

Also one thing you need to know (which took me a while to figure out) is that you can't call jest.mock() inside the test; you must call it at the top level of the module. However, you can call mockImplementation() inside individual tests if you want to set up different mocks for different tests.

8
  • 33
    the key that helped me to make it work was this "you can't call jest.mock() inside the test; you must call it at the top level of the module" Commented Sep 30, 2020 at 21:06
  • 11
    The reason that you must have jest.mock at the top of your tests, is internally jest will reorder the jest.mock before the imports. This is why it doesn't matter if yoour jest.mock is before or after your import. By placing it in a function body, it will not function correctly. Commented Dec 21, 2020 at 13:07
  • 2
    The __esModule: true made it work where I needed to mock default exports. Otherwise it worked well without that. Thanks for that answer! Commented Jul 27, 2021 at 9:56
  • 1
    I'm not clear what 'mockedDefaultExport' is supposed to be -- why isn't it a variable like mockFunction vs a string like 'mockFunction'? why not make them both jest.fn()?
    – jcollum
    Commented Aug 23, 2021 at 16:26
  • 1
    @jcollum I think it's just illustrating that any export (including the default export) could be a string just as as easily as it could be a function, and it can be mocked in the same way
    – Andy
    Commented Aug 24, 2021 at 10:06
73

To mock an ES6 dependency module default export using Jest:

import myModule from '../myModule';
import dependency from '../dependency';

jest.mock('../dependency');

// If necessary, you can place a mock implementation like this:
dependency.mockImplementation(() => 42);

describe('myModule', () => {
  it('calls the dependency once with double the input', () => {
    myModule(2);

    expect(dependency).toHaveBeenCalledTimes(1);
    expect(dependency).toHaveBeenCalledWith(4);
  });
});

The other options didn't work for my case.

7
  • 11
    what's the best way to clean this up if I just want to make for one test? inside afterEach? ```` afterEach(() => { jest.unmock(../dependency'); }) ````
    – nazreen
    Commented Oct 7, 2017 at 6:12
  • 1
    @falsarella does doMock actually work in that case ? I'm having very similar issue and it does nothing when I'm trying to jest.doMock inside specific test, where jest.mock for whole module is working correctly
    – pgrzesik
    Commented Feb 19, 2018 at 15:47
  • 1
    @Progress1ve you can try using jest.mock with mockImplementationOnce as well
    – falsarella
    Commented Feb 19, 2018 at 17:04
  • 1
    Yup, that's a valid suggestion, however that requires the test to be the first one and I'm not a fan of writing tests in such a way. I got around those issues by importing external module and using spyOn on specific functions.
    – pgrzesik
    Commented Feb 23, 2018 at 23:04
  • 1
    @Progress1ve hmm I meant to place the mockImplementationOnce inside each specific test... anyway, I'm happy you've found a solution :)
    – falsarella
    Commented Feb 23, 2018 at 23:44
42

Adding more to Andreas' answer. I had the same problem with ES6 code, but I did not want to mutate the imports. That looked hacky. So I did this:

import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency');

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    myModule(2);
  });
});

And added file dependency.js in the " __ mocks __" folder parallel to file dependency.js. This worked for me. Also, this gave me the option to return suitable data from the mock implementation. Make sure you give the correct path to the module you want to mock.

6
  • Thanks for this. Will give it a try. Liked this solution too - stackoverflow.com/a/38414160/1882064
    – arcseldon
    Commented Jan 14, 2017 at 14:29
  • 1
    What I like about this approach is that it gives you the possibility to provide one manual mock for all occasions in which you want to mock a specific module. I for example, have a translation helper, which is used in many places. The __mocks__/translations.js file simply default exports jest.fn() in something like: export default jest.fn((id) => id) Commented Feb 1, 2017 at 17:30
  • You can also use jest.genMockFromModule to generate mocks from modules. facebook.github.io/jest/docs/… Commented Jun 20, 2017 at 11:33
  • 2
    One thing to note is that ES6 modules mocked via export default jest.genMockFromModule('../dependency') will have all of their functions assigned to dependency.default after calling `jest.mock('..dependency'), but otherwise behave as expected.
    – jhk
    Commented Jul 14, 2017 at 9:14
  • 7
    What does your test assertion look like? That seems like an important part of the answer. expect(???)
    – stone
    Commented Aug 21, 2017 at 21:25
25

None of the answers here seemed to work for me (the original function was always being imported rather than the mock), and it seems that ESM support in Jest is still work in progress.

After discovering this comment, I found out that jest.mock() does not actually work with regular imports, because the imports are always run before the mock (this is now also officially documented). Because of this, I am importing my dependencies using await import(). This even works with a top-level await, so I just have to adapt my imports:

import { describe, expect, it, jest } from '@jest/globals';

jest.unstable_mockModule('../dependency', () => ({
  doSomething: jest.fn()
}));

const myModule = await import('../myModule');
const dependency = await import('../dependency');

describe('myModule', async () => {
  it('calls the dependency with double the input', () => {
    myModule(2);
    expect(dependency.doSomething).toBeCalledWith(4);
  });
});
7
  • 1
    most promising alternative, but: ReferenceError: require is not defined Commented Sep 30, 2022 at 10:41
  • 1
    @GabrielAnderson My code doesn't use require, I assume you are transpiling your code to CommonJS.
    – cdauth
    Commented Sep 30, 2022 at 13:01
  • 1
    Worked for me. Order of code, await import, and unstable_mockModule were the keys.
    – Julian
    Commented Apr 28, 2023 at 19:44
  • @GabrielAnderson if you are using jest with ts-jest to transform typescript you can must also instruct jest which files it should treat as esm. ``` #jest.config.js extensionsToTreatAsEsm: ['.ts'], transform: { '^.+\\.ts$': [ 'ts-jest', { useESM: true, }, ], }, ```
    – ThdK
    Commented Aug 17, 2023 at 4:22
  • Cool, I'll try latter. But no, I just use vanilla JS, without any parser. Commented Aug 17, 2023 at 14:13
13

The question is already answered, but you can resolve it like this:

File dependency.js

const doSomething = (x) => x
export default doSomething;

File myModule.js

import doSomething from "./dependency";

export default (x) => doSomething(x * 2);

File myModule.spec.js

jest.mock('../dependency');
import doSomething from "../dependency";
import myModule from "../myModule";

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    doSomething.mockImplementation((x) => x * 10)

    myModule(2);

    expect(doSomething).toHaveBeenCalledWith(4);
    console.log(myModule(2)) // 40
  });
});
6
  • 2
    But "require" is CommonJS syntax - OP was asking about ES6 Modules
    – Andy
    Commented Mar 12, 2020 at 14:32
  • @Andy thanks for your comment, I updated my answer. BTW same thing in the logic.
    – Slim
    Commented Mar 12, 2020 at 22:11
  • 2
    How can you call .mockImplementation on doSomething before mocking it?
    – T J
    Commented Nov 11, 2021 at 7:01
  • 1
    I think this answer needs some ellaboration, i have the same question as above Commented Dec 10, 2021 at 16:25
  • @T J @Alexander Santos doSomethingis mocked automatically in line 1, which mocks the whole module ../dependency.
    – idleherb
    Commented Sep 7, 2022 at 10:43
3

I solved this another way. Let's say you have your dependency.js

export const myFunction = () => { }

I create a depdency.mock.js file besides it with the following content:

export const mockFunction = jest.fn();

jest.mock('dependency.js', () => ({ myFunction: mockFunction }));

And in the test, before I import the file that has the dependency, I use:

import { mockFunction } from 'dependency.mock'
import functionThatCallsDep from './tested-code'

it('my test', () => {
    mockFunction.returnValue(false);

    functionThatCallsDep();

    expect(mockFunction).toHaveBeenCalled();

})
1
  • 2
    this isn't valid Jest. you'll receive an error like this: The module factory of jest.mock() is not allowed to reference any out-of-scope variables.
    – so001
    Commented Feb 4, 2021 at 20:13
3

I tried all the solutions and none worked or were showing lots of TS errors.

This is how I solved it:

format.ts file:

import camelcaseKeys from 'camelcase-keys'
import parse from 'xml-parser'

class Format {
  parseXml (xml: string) {
    return camelcaseKeys(parse(xml), {
      deep: true,
    })
  }
}

const format = new Format()
export { format }

format.test.ts file:

import format from './format'
import camelcaseKeys from 'camelcase-keys'
import parse from 'xml-parser'

jest.mock('xml-parser', () => jest.fn().mockReturnValue('parsed'))
jest.mock('camelcase-keys', () => jest.fn().mockReturnValue('camel cased'))

describe('parseXml', () => {
  test('functions called', () => {
    const result = format.parseXml('XML')

    expect(parse).toHaveBeenCalledWith('XML')
    expect(camelcaseKeys).toHaveBeenCalledWith('parsed', { deep: true })
    expect(result).toBe('camel cased')
  })
})
1
  • having the mock value as parameter worked for me thanks! Commented Feb 28, 2023 at 17:26
-1

I made some modifications on @cam-jackson original answer and side effects has gone. I used lodash library to deep clone the object under test and then made any modification I want on that object. But be ware that cloning heavy objects can have negative impact on test performance and test speed.

objectUndertest.js

const objectUnderTest = {};
export default objectUnderTest;

objectUnderTest.myFunctionUnterTest = () => {
  return "this is original function";
};

objectUndertest.test.js

import _ from "lodash";
import objectUndertest from "./objectUndertest.js";

describe("objectUndertest", () => {
  let mockObject = objectUndertest;

  beforeEach(() => {
    mockObject = _.cloneDeep(objectUndertest);
  });

  test("test function", () => {
    mockObject.myFunctionUnterTest = () => {
      return "this is mocked function.";
    };

    expect(mockObject.myFunctionUnterTest()).toBe("this is mocked function.");
  });
});

Not the answer you're looking for? Browse other questions tagged or ask your own question.