How to Test ECMAScript Modules with 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:
mkdir jest-esm && cd jest-esmnpm init -yInstall the necessary development dependencies:
npm i -D typescript jest @types/jest ts-node ts-jestAdd "type": "module" to the package.json.
Your package.json should look like this:
{ "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:
npx tsc --initUpdate 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
npm init jest@latestEdit 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:
npm i hast-util-from-htmlThe 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:
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:
import JestEsm from './index';
test('case1', () => { JestEsm();});Run the tests:
npm run testIf 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-jsdomRun the tests again:
npm run testExpected output:
PASS ./index.spec.ts ✓ case1 (20 ms)
Test Suites: 1 passed, 1 totalTests: 1 passed, 1 totalSnapshots: 0 totalTime: 1 sRan all test suites.