Reputation: 19941
My setup
import
).package.json
I have a script (following Jest documentation adapted to Windows): "test": "set \"NODE_OPTIONS=--experimental-vm-modules\" && npx jest"
npm run test
(the script above)My goal: Run the ESM tests using jest.
My attempts:
package.json
contains "type": "commonjs"
test.js
SyntaxError: Cannot use import statement outside a module
(This is expected because the test is being run as a commonjs file.)package.json
contains "type": "commonjs"
test.mjs
No tests found
(Jest isn't finding the file?)package.json
now has "type": "module"
test.mjs
or test.js
ReferenceError: module is not defined at ...jest.config.js:6:1
package.json
contains "type": "commonjs"
test.js
but using the commonjs require
syntax (and without the ESM modules I'd like to include); Note that this is not what I want, but included just to show there's nothing wrong with the test itself.Upvotes: 64
Views: 64982
Reputation: 4830
Using ECMAScript modules, Node v20.17.0
and Jest 29.7.0
, it was enough to add "type": "module", "test" : "NODE_OPTIONS='--experimental-vm-modules' npx jest"
to my package.json
.
Upvotes: 1
Reputation: 165
Since this is the highest voted/rated questions about Jest and ESM, I'll post what I managed to do. Mostly so that I'll find it again when I'll forget all about it.
Tl;Dr: It's.. complicated..
Firstly, read this: https://jestjs.io/docs/ecmascript-modules
My situation
tsc
, and the code below is basically JSThe code
client.ts
import { simpleFn, simpleFnAsync, functionOfAFunction, functionOfAFunctionAsync } from './library.js';
function simpleFnCaller() {
console.debug('simpleFnCaller');
simpleFn({ p1: 10, p2: 20 });
}
async function simpleFnAsyncCaller() {
console.debug('simpleFnAsyncCaller');
await simpleFnAsync({ p1: 10, p2: 20 });
}
function functionOfAFunctionCaller() {
console.debug('functionOfAFunctionCaller');
const fn = functionOfAFunction({ p1: 10, p2: 20 });
fn();
}
async function functionOfAFunctionAsyncCaller() {
console.debug('functionOfAFunctionAsync');
const fn = functionOfAFunctionAsync({ p1: 10, p2: 20 });
await fn();
}
async function runAll() {
simpleFnCaller();
await simpleFnAsyncCaller();
functionOfAFunctionCaller();
await functionOfAFunctionAsyncCaller();
}
export default runAll;
library.ts
export function simpleFn({ p1, p2 }: { p1: number, p2: number }) {
console.debug(`simpleFn function called. p1: ${p1}, p2: ${p2}`);
};
export async function simpleFnAsync({ p1, p2 }: { p1: number, p2: number }) {
console.debug(`simpleFnAsync function called. p1: ${p1}, p2: ${p2}`);
};
export function functionOfAFunction({ p1, p2 }: { p1: number, p2: number }) {
console.debug(`functionOfAFunction function called. p1: ${p1}, p2: ${p2}`);
return function () {
console.debug(`return value of functionOfAFunction function called. p1: ${p1}, p2: ${p2}`);
};
};
export function functionOfAFunctionAsync({ p1, p2 }: { p1: number, p2: number }) {
console.debug(`functionOfAFunctionAsync function called. p1: ${p1}, p2: ${p2}`);
return async function () {
console.debug(`return value of functionOfAFunctionAsync function called. p1: ${p1}, p2: ${p2}`);
};
};
test.ts
import { jest } from '@jest/globals';
// You need to call this _this way_ and _before_ importing the stuff to mock.
// If you `import`, it will be statically imported and you can kiss your mocks goodbye.
jest.unstable_mockModule('../src/library.js', () => {
return {
__esModule: true, // Not sure if it's needed but at one point it threw errors without
simpleFn: jest.fn(({ p1, p2 }: { p1: number, p2: number }) => { }), // For Typescript, the signature must match.
simpleFnAsync: jest.fn(({ p1, p2 }: { p1: number, p2: number }) => { }),
functionOfAFunction: jest.fn(({ p1, p2 }: { p1: number, p2: number }) => { () => { } }), // The return value matters.
functionOfAFunctionAsync: jest.fn(({ p1, p2 }: { p1: number, p2: number }) => { async () => { } }),
};
});
// Now we dynamically import the dependencies..
const { simpleFn, simpleFnAsync, functionOfAFunction, functionOfAFunctionAsync } = await import('../src/library.js');
// Now we dynamically import the code to test..
const master = await import('../src/client.js');
// If you return a function, and want to test that return value, you need to set it up. Again.
(functionOfAFunction as jest.Mock).mockReturnValue(jest.fn());
(functionOfAFunctionAsync as jest.Mock).mockReturnValue(jest.fn());
describe('Testing master', () => {
beforeEach(async () => {
jest.clearAllMocks();
// Calling the code to test, this way, because reasons.
master.default();
});
it('simpleFnCaller calls simpleFn with correct parameters', () => {
expect(simpleFn).toHaveBeenCalledWith({ p1: 10, p2: 20 });
});
it('simpleFnAsyncCaller calls simpleFnAsync with correct parameters', async () => {
await new Promise(process.nextTick); // Wait for async function to complete
expect(simpleFnAsync).toHaveBeenCalledWith({ p1: 10, p2: 20 });
});
it('functionOfAFunctionCaller calls functionOfAFunction and the returned function', () => {
const returnedFn = (functionOfAFunction as jest.Mock).mock.results[0].value;
expect(functionOfAFunction).toHaveBeenCalledWith({ p1: 10, p2: 20 });
expect(returnedFn).toHaveBeenCalled();
});
it('functionOfAFunctionAsyncCaller calls functionOfAFunctionAsync and the returned function', async () => {
const returnedFn = (functionOfAFunctionAsync as jest.Mock).mock.results[0].value;
await new Promise(process.nextTick); // Wait for async function to complete
expect(functionOfAFunctionAsync).toHaveBeenCalledWith({ p1: 10, p2: 20 });
expect(returnedFn).toHaveBeenCalled();
});
});
To run: NODE_OPTIONS="--enable-source-maps --experimental-vm-modules" npx jest -t 'Testing master'
Upvotes: 0
Reputation: 864
After a lot of fiddling, this is my successful setup (the parts that matter):
package.json
has
"type": "commonjs",
"scripts": {
"test": "NODE_OPTIONS=--experimental-vm-modules jest"
}
jest.config.mjs
(yes, esModule)
export default {
moduleFileExtensions: [
"mjs",
// must include "js" to pass validation https://github.com/facebook/jest/issues/12116
"js",
],
testRegex: `test\.mjs$`,
};
That having to include "js"
is a historic relic from the times modules where obviously .js files. Follow https://github.com/facebook/jest/issues/12116 for update.
Upvotes: 13
Reputation: 1979
Installing esm
npm package
npm i --save-dev esm
did the trick for me without any additional jest configuration. Cause ESM package has a require
function that is able to substitute for CJS require. So you'll be overriding the CJS require function with ESM one in every jest file that tests ESM.
So for instance to test yourESModule.js
which is an ESM with jest, you would start your yourESModule.test.js
with the following:
require = require('esm')(module)
const { yourFunction } = require('./yourESModule')
Btw, my jest version is "jest": "^29.6.1"
Then just run:
npx jest
Hope this helps!
Upvotes: 0
Reputation: 246
For me to work it was only necessary to set script "test" as follows:
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules npx jest"
and yeah, don't forget -> npm i cross-env jest
Upvotes: 11
Reputation: 13560
Here are the steps I took to run Jest with a test using ESM. The source files under test were also written using ESM.
Set my node version to 14.16.0
Install Jest:
npm i jest -D
Add "type": "module"
to package.json
Update test
script in package.json
:
"scripts": {
"test": "node --experimental-vm-modules ./node_modules/.bin/jest"
}
Create a jest.config.js
file with the following content:
export default { transform: {} }
Have at least one test that could be found using the default testMatch
(mine was inside __tests__
dir)
Run tests:
npm test
There is a more complete example in the magic-comments-loader repository on GitHub.
Upvotes: 57
Reputation: 770
I followed the @morganney answer but I was getting this error: "basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")..."
. so to make it work I used the property
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
from the documentation.
Thus my package.json ended up configured like this:
{
"type": "module",
"jest": {
"transform": {}
},
"scripts": {
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
}
"dependencies": {
"jsdom": "^17.0.0",
}
}
Upvotes: 2
Reputation: 556
in my case, leaving
"type": "module"
in package.jsonand renaming
jest.config.js
to jest.config.cjs
babel.config.js
to babel.config.cjs
worked
Upvotes: 4
Reputation: 352
Here's the minimum you need. Props to @Luke for the comment above with the testMatch answer I needed.
package.json
- Make sure "type": "module"
is in there
jest.config.js
- Add this glob to your testMatch
array: "**/?(*.)+(spec|test).(m)js"
Make sure your test file(s) end in .spec.mjs
or .test.mjs
to match the glob
Set your npm test
script to node --experimental-vm-modules ./node_modules/.bin/jest
That's it. You do not need the transform setting in the jest config as the documentation suggests.
Upvotes: 3