How to Mock an Es6 Module Import Using Jest

How can I mock an ES6 module import using Jest?

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.

How to jest.mock an ES6 module import?

Have you try to mock like this

jest.mock('@mine/my-lovely-libary', () => ({
doSomething: () => doSomethingMock,
}));

here doSomething is a method from your npm library

doSomethingMock can be jest.fn() or something like this const doSomethingMock = 'mockTestValue'

Jest Module Mock with ECMAScript Modules

Although hoisting is done in common JS, it is not done using ECMAScript Modules, so instead of importing mocked modules, you must use dynamic import to import them after the mocking.

test/test.js:

import {jest} from '@jest/globals';
jest.mock('ethers', () => ({ethers: 'Hello, world!'}));
const {ethers} = await import('ethers');

test('do it', ()=> {
expect(ethers).toEqual("Hello, world!");
});

package.json:

{
"scripts": {
"watch-test": "jest ./test --verbose --watch"
},
"dependencies": {
"ethers": "^5.6.9"
},
"devDependencies": {
"jest": "^28.1.3"
},
"jest": {
"verbose": true,
"testMatch": [
"<rootDir>/test/**/*.?(c)js"
],
"transform" : {}
},
"type": "module"
}

("testMatch": "test/**" doesn't work, you must prefix with <rootDir> or **; I'm not sure why.)

And invoked as stated in the question.

This conversation got me on the right track: https://github.com/facebook/jest/issues/13135

mock one function using jest.mock() and not another in the same file

mock will not replace the original source-code file, but only add a new mocked version.

import { add } from './main';

jest.mock('./main', () => {
const actual = jest.requireActual('./main');
return {
// ...actual,
add: function () {
return 17;
}
};
});

describe('add', () => {
it('should use the mocked add function', () => {
expect(add(1, 2)).toBe(17);
});

it('should use the original add function', async () => {
const main = jest.requireActual('./main');
expect(main.add(1, 2)).toBe(3);
});
});

In your mock-factory function you replace add, but you use the original calculate function, which calls the original add function. This is why the mocked add function is not used.

To make it work the way you want, you also have to replace calculate (so that it can use the new add function), e.g.:

import { add, calculate, minus } from './main';

jest.mock('./main', () => ({
//__esModule: true,
...jest.requireActual('./main'),
calculate: function (x: number, y: number, operator: string) {
let result = -1;
switch (operator) {
case '+':
result = add(x, y);
break;
case '-':
result = minus(x, y);
break;
}
return result;
},
add: function () {
return 17;
}
}));

describe('calculate', () => {
it('should use the mocked calculate and add function', () => {
const result = calculate(3, 2, '+');
expect(result).toBe(17);
});
});

Since all these replacements are confusing and error prone, you should really use jest.spyOn instead.

Quote from this answer

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

side note

If you really want to go this way, then you should at least move the operators and the calculate function to different files. Then you only need to mock the operators module.

operators.ts:

export function add(x: number, y: number): number {
return x + y;
}

export const minus = (x: number, y: number): number => {
return x - y;
};

calculate.ts:

import { add, minus } from './operators';

export const calculate = (x: number, y: number, operator: string) => {
let result = -1;
switch (operator) {
case '+':
result = add(x, y);
break;
case '-':
result = minus(x, y);
break;
}
return result;
};

calculate.spec.ts:

import { calculate } from './calculate';

jest.mock('./operators', () => ({
//__esModule: true,
...jest.requireActual('./operators'),
add: function () {
return 11;
}
}));

describe('calculate', () => {
it('should use the mocked add function', () => {
const result = calculate(3, 2, '+');
expect(result).toBe(11);
});
});


Related Topics



Leave a reply



Submit