Testing ECMAScript Modules (ESM) with Jest: A Step-by-Step Guide
Introduction
When testing ECMAScript Modules (ESM) using Jest, you might encounter the error:
SyntaxError: Cannot use import statement outside a module
This issue arises because the target modules use the import
keyword. Fortunately, Jest provides experimental support for ESM to resolve such errors.
Jest ships with experimental support for ECMAScript Modules (ESM).
In this guide, you’ll learn how to configure Jest and resolve these errors using an example ESM package.
Setting Up the Project
Creating an ESM Package
First, create a new ESM package with the following commands:
mkdir jest-esm && cd jest-esm
npm init -y
Install the necessary development dependencies:
npm i -D typescript jest @types/jest ts-node ts-jest
Your package.json
should look like this:
{
"name": "jest-esm",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@types/jest": "^29.5.12",
"jest": "^29.7.0",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
}
}
Add "type": "module"
to the package.json
.
@@ -3,6 +3,7 @@
"version": "1.0.0",
"description": "",
"main": "index.js",
+ "type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
Configuring TypeScript
Generate a tsconfig.json
file with the following command:
npx tsc --init
Update the tsconfig.json
file:
@@ -14 +14 @@
- "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+ "target": "es6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
@@ -28 +28 @@
- "module": "commonjs", /* Specify what module code is generated. */
+ "module": "es6", /* Specify what module code is generated. */
@@ -30 +30 @@
- // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
+ "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
Configuring Jest
Run the following command to set up Jest configuration:
npm init jest@latest
Follow these options:
Option | Value |
---|---|
Use Jest for the “test” script | Yes |
Use TypeScript for the configuration | Yes |
Test environment | jsdom |
Add coverage reports | No |
Provider for coverage | v8 |
Automatically clear mock calls… | No |
Edit the generated jest.config.ts
to include:
- // preset: undefined,
+ preset: 'ts-jest',
Supporting ESM on Jest
To enable ESM testing in Jest, follow these steps.
Modifying package.json
Update the test
script in package.json
to enable Node’s experimental VM modules:
"scripts": {
- "test": "jest"
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
}
Updating jest.config.ts
Add the extensionsToTreatAsEsm
configuration to indicate which file types Jest should treat as ESM:
+ extensionsToTreatAsEsm: ['.ts'],
Supporting ESM on ts-jest
To configure ESM in ts-jest
, refer to the official documentation.
Configuration Changes
Update jest.config.ts
with moduleNameMapper
to handle .js
extensions:
- // moduleNameMapper: {},
+ moduleNameMapper: {
+ '^(\\.{1,2}/.*)\\.js$': '$1',
+ },
Update the transform
setting to support ESM:
- // transform: undefined,
+ transform: {
+ '^.+\\.tsx?$': [
+ 'ts-jest',
+ {
+ useESM: true,
+ },
+ ],
+ },
Creating ESM
Installing Dependencies
For demonstration, install the hast-util-from-html
package:
npm i hast-util-from-html
The module hast-util-from-html
is an ECMAScript Module (ESM) and uses the export
keyword for its exports. This can be confirmed by examining the file located at node_modules/hast-util-from-html/index.js
. Here’s a snippet of the module’s export structure:
/**
* @typedef {import('hast-util-from-parse5')} DoNotTouchItRegistersData
*
* @typedef {import('./lib/index.js').ErrorCode} ErrorCode
* @typedef {import('./lib/index.js').ErrorSeverity} ErrorSeverity
* @typedef {import('./lib/index.js').OnError} OnError
* @typedef {import('./lib/index.js').Options} Options
*/
export { fromHtml } from './lib/index.js';
Creating the Module
Create index.ts
:
import { fromHtml } from 'hast-util-from-html';
export default function JestEsm(): void {
const root = fromHtml(
'<span><a href="https://github.com">GitHub</a></span>',
{ fragment: true }
);
console.info(root);
}
Writing Tests
Create index.spec.ts
:
import JestEsm from './index';
test('case1', () => {
JestEsm();
});
Testing ESM
Run the tests:
npm run test
If you see the error:
● Validation Error:
Test environment jest-environment-jsdom cannot be found. Make sure the testEnvironment configuration option points to an existing node module.
Configuration Documentation:
https://jestjs.io/docs/configuration
As of Jest 28 "jest-environment-jsdom" is no longer shipped by default, make sure to install it separately.
Install the missing package:
npm i -D jest-environment-jsdom
Run the tests again:
npm run test
Expected output:
PASS ./index.spec.ts
✓ case1 (20 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1 s
Ran all test suites.
Conclusion
Testing ESM with Jest requires careful configuration due to its experimental support. By following this guide, you can resolve import errors and effectively test ESM packages.
For additional details, refer to the Jest ESM Documentation.
Happy Coding! 🚀