How to Test ECMAScript Modules with Jest

How to Test ECMAScript Modules with Jest

Takahiro Iwasa
Takahiro Iwasa
4 min read
Jest

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.

Creating ESM Package

First, create a new ESM package with the following commands:

Terminal window
mkdir jest-esm && cd jest-esm
npm init -y

Install the necessary development dependencies:

Terminal window
npm i -D typescript jest @types/jest ts-node ts-jest

Add "type": "module" to the package.json.

Your package.json should look like this:

package.json
{
"name": "jest-esm",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"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"
}
}

Configuring TypeScript

Generate a tsconfig.json file with the following command:

Terminal window
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:

  • 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
Terminal window
npm init jest@latest

Edit the generated jest.config.ts to include:

// preset: undefined,
preset: 'ts-jest',

Supporting ESM

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"
}

Add the extensionsToTreatAsEsm configuration in jest.config.ts to indicate which file types Jest should treat as ESM:

extensionsToTreatAsEsm: ['.ts'],

To configure ESM in ts-jest, refer to the official documentation.

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

For demonstration, install the hast-util-from-html package:

Terminal window
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';

Create index.ts:

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

Testing ESM

Create index.spec.ts:

index.spec.ts
import JestEsm from './index';
test('case1', () => {
JestEsm();
});

Run the tests:

Terminal window
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:

Terminal window
npm i -D jest-environment-jsdom

Run the tests again:

Terminal window
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.
Takahiro Iwasa

Takahiro Iwasa

Software Developer
Involved in the requirements definition, design, and development of cloud-native applications using AWS. Japan AWS Top Engineers 2020-2023.