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 import
ing 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
Regex to Match All Instances Not Inside Quotes
Add Event Listener on Elements Created Dynamically
Global Variables in JavaScript Across Multiple Files
Accessing Redux State in an Action Creator
Upload File with Ajax Xmlhttprequest
How to Use Window.Postmessage Across Domains
How to Get the HTML for a Dom Element in JavaScript
JavaScript - Head, Body or Jquery
Making a Chrome Extension Download a File
Document.Getelementsbyclassname().Innerhtml Always Returns "Undefined"
What Does the "|" (Single Pipe) Do in JavaScript
Parse Date Without Timezone JavaScript
Web Workers Without a Separate JavaScript File
JavaScript Date Regex Dd/Mm/Yyyy
How to Access an SQLite Database from JavaScript