417

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.

Peter Mortensen
  • 30,030
  • 21
  • 100
  • 124
Cam Jackson
  • 11,101
  • 6
  • 43
  • 73
  • I'm using Babel for this project anyway, so I don't mind continuing to transpile `import`s to `require`s for now. Thanks for the heads up though. – Cam Jackson Nov 07 '16 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() – kailash yogeshwar Nov 03 '17 at 09:41
  • 1
    for those who want to discover this issue more https://github.com/facebook/jest/issues/936 – omeralper Feb 12 '19 at 00:18

8 Answers8

294

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

naXa stands with Ukraine
  • 30,969
  • 16
  • 175
  • 237
Cam Jackson
  • 11,101
  • 6
  • 43
  • 73
  • Thanks for sharing. I think the net result is similar to this - but this might be cleaner - http://stackoverflow.com/a/38414160/1882064 – arcseldon Jan 14 '17 at 14:27
  • That was really helpful...thanks! Here's a useful variation: My dependency was a constant (e.g. `import { MY_CONSTANT } from './dependency';`) whose value might change over the course of development but which I wanted to keep fixed in my test. In this case using `jest.fn()` as you did doesn't make sense so, in the test, after using `import * ...` as you suggest, I simplified the mutation and just used `dependency.MY_CONSTANT = 42;`. – Andrew Willems Jan 22 '17 at 13:18
  • Why don't you use jest.mock() in your examples ? I don't have the answer, just asking. Your solution works but I don't feel it's clean. – Gabriel Apr 26 '17 at 15:45
  • This worked for me, but Flow does not like the mutation of module exports: `Error:(101, 3) Flow: assignment of property 'xxx'. Mutation not allowed on exports of "./moduleName"`. – Andrii Chernenko May 09 '17 at 18:33
  • 81
    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. – Mihai Damian May 23 '17 at 13:58
  • 16
    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): https://medium.com/@rickhanlonii/understanding-jest-mocks-f0046c68e53c . – Martinsos Jul 05 '18 at 13:29
  • 3
    Just a note: if the `dependency` is reside on the same file as `myModule`, it will not work. – Lu Tran Nov 15 '18 at 19:04
  • I wonder if Jest team can make this a little less painful, without requiring the hacking around of `import *` – Lu Tran Nov 15 '18 at 19:13
  • it would good to know how this hack really works. I had a hard time explaining this is bad practice since it worked. I almost thought it was NOT allowed (OR should not be allowed). Some insight would help ... – trk Mar 08 '19 at 08:34
  • this way of mocking is fine, as long you resetMock after each test to reset the mutations. e.g afterEach(() => { dependency.mockReset(); }); – deviantxdes Apr 11 '20 at 13:02
  • 31
    I think this won't work with Typescript the object you're mutating is read-only. – anlogg May 05 '20 at 15:11
  • 1
    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. – Piepongwong Nov 22 '20 at 15:46
  • 1
    @adredx You can use `spyOn`. it will work perfectly with typescript and you can restore the original method later – Yusufbek Feb 28 '21 at 20:48
  • How does Jest change/mock the original default or named export when it is imported and `jest.mock("package-name")` is called? – tonix Jan 06 '22 at 16:26
243

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);
  });
});
Andreas Köberle
  • 98,250
  • 56
  • 262
  • 287
  • 4
    This doesn't seem right. I get: `babel-plugin-jest-hoist: The second argument of jest.mock must be a function.` So the code's not even compiling. – Cam Jackson Nov 07 '16 at 22:13
  • So, I changed your code so that the second argument to `jest.mock` is a function returning the module object, rather than just specifying the fake module directly. It still doesn't work with ES6 imports though - in both the test and implementation code, the imported module is the original one, rather than the mock one with a spy inside it. – Cam Jackson Nov 08 '16 at 02:20
  • 5
    Sorry, I've update my code. Please also note that the path in `jest.mock` is relative to the test file. – Andreas Köberle Nov 08 '16 at 07:34
  • 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. – Cam Jackson Nov 08 '16 at 11:35
  • 1
    This did work for me, however, not when using default exports. – Iris Schaffer Feb 01 '17 at 17:28
  • 1
    The solution works for me. But this answer looks neater. Could you provide an example for named exports, please? – zyxue Feb 08 '17 at 07:32
  • 5
    @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. – Johannes Lumpe May 03 '17 at 20:53
  • 38
    Mocking default exports: ```jest.mock('../dependency', () => ({ default: jest.fn() }))``` – Neob91 Jun 13 '17 at 15:19
  • 1
    Cam Jackson's answer didn't work for me because of tslint. But this solution worked great. I just did jest.mock('../MyFunctionFile', () => ({ myfunction: jest.fn(() => { return any; }) })); – Violaman Jul 16 '18 at 18:44
150

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.

Peter Mortensen
  • 30,030
  • 21
  • 100
  • 124
Andy
  • 9,149
  • 11
  • 61
  • 85
  • 26
    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" – Joe Cabezas Sep 30 '20 at 21:06
  • 4
    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. – Geoff Lentsch Dec 21 '20 at 13:07
  • 1
    The `__esModule: true` made it work where I needed to mock default exports. Otherwise it worked well without that. Thanks for that answer! – Andreas Dolk Jul 27 '21 at 09:56
  • 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 Aug 23 '21 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 Aug 24 '21 at 10:06
  • Question - can you extract the mocked module to another testUtils file, and call it? Like this ``` // testUtils.ts export const mockNamedExport = () => { jest.mock('./esModule', () => ({ __esModule: true, // this property makes it work default: 'mockedDefaultExport', namedExport: jest.fn(), })); } // test file import {mockNamedExport} from './testUtils'; mockNamedExport(); ``` – curtybear Oct 09 '21 at 11:13
  • I found the blog post referenced before I found this post... You are all much smarter than me. Here is the blog post that [worked for me](https://mikeborozdin.com/post/changing-jest-mocks-between-tests/) He is saying the same thing, I just found it easier to follow. `jest.mock('./config', () => ({ __esModule: true, default: null }));` is the important bit. – Chris Huang-Leaver Nov 18 '21 at 22:42
70

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.

Peter Mortensen
  • 30,030
  • 21
  • 100
  • 124
falsarella
  • 12,098
  • 8
  • 69
  • 112
  • 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'); }) ```` – nxmohamad Oct 07 '17 at 06:12
  • @nxmohamad In your case, I'd use [`jest.doMock`/`jest.dontMock`](https://facebook.github.io/jest/docs/en/jest-object.html#jestdomockmodulename-factory-options) inside that specific test itself, instead of [`jest.mock`/`jest.unmock`](https://facebook.github.io/jest/docs/en/jest-object.html#jestmockmodulename-factory-options) - that's because they are hoisted to the top of the test file, impacting the other tests as well, and that's not what you want, right? Take a look into [Jest documentation](https://facebook.github.io/jest/docs/en/jest-object.html#content) for more details and examples. – falsarella Oct 09 '17 at 22:34
  • 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 – Progress1ve Feb 19 '18 at 15:47
  • 1
    @Progress1ve you can try using jest.mock with mockImplementationOnce as well – falsarella Feb 19 '18 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. – Progress1ve Feb 23 '18 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 Feb 23 '18 at 23:44
  • Doesn't using mockImplementationOnce still force you to mock the whole module with jest.mock first ? – Progress1ve Feb 25 '18 at 00:14
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.

Peter Mortensen
  • 30,030
  • 21
  • 100
  • 124
mdsAyubi
  • 1,187
  • 8
  • 9
  • Thanks for this. Will give it a try. Liked this solution too - http://stackoverflow.com/a/38414160/1882064 – arcseldon Jan 14 '17 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)``` – Iris Schaffer Feb 01 '17 at 17:30
  • You can also use `jest.genMockFromModule` to generate mocks from modules. https://facebook.github.io/jest/docs/jest-object.html#jestgenmockfrommodulemodulename – Varunkumar Nagarajan Jun 20 '17 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 Jul 14 '17 at 09:14
  • 7
    What does your test assertion look like? That seems like an important part of the answer. `expect(???)` – stone Aug 21 '17 at 21:25
  • This is my preferred answer, and I was using it but now I need to have N mocks of the same function, representing N-types of return. (eg: when mocking a function that calls a rest endpoint, have one that returns one status code and content, another that returns a different one etc) How would I do this? – ndtreviv Jan 29 '19 at 09:31
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
  });
});
Peter Mortensen
  • 30,030
  • 21
  • 100
  • 124
Slim
  • 4,471
  • 10
  • 40
  • 72
4

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

})
Peter Mortensen
  • 30,030
  • 21
  • 100
  • 124
Felipe Leusin
  • 19,815
  • 2
  • 25
  • 19
  • 1
    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. – tshm001 Feb 04 '21 at 20:13
2

None of the answers here seemed to work for me, 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 really work with regular imports, because the imports are always run before the mock. Because of this, I am importing my dependencies using async import():

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

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

describe('myModule', async () => {
  const myModule = await import('../myModule');
  const dependency = await import('../dependency');

  it('calls the dependency with double the input', () => {
    myModule(2);
    expect(dependency.doSomething).toBeCalledWith(4);
  });
});
cdauth
  • 5,026
  • 3
  • 32
  • 37