Jest で ECMAScript Modules をテストする

Jest で ECMAScript Modules をテストする

package.json、TypeScript、Jest の設定を連携させ、Jest の "Cannot use import statement outside a module" エラーを解消する方法を紹介します。

Takahiro Iwasa
6 min read

Jest を使って ECMAScript Modules (ESM) をテストすると、次のエラーが発生することがあります。

SyntaxError: Cannot use import statement outside a module

これは、Jest が import を含むファイルを CommonJS として実行した場合に発生します。Jest は ESM の実験的サポート を提供していますが、Node.js、TypeScript、Jest の各設定で一貫して有効にする必要があります。

ESM パッケージの作成

まず、次のコマンドで新しい ESM パッケージを作成します。

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

必要な開発用依存関係をインストールします。

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

package.json"type": "module" を追加します。

package.json は次のようになります。

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

TypeScript の設定

次のコマンドで tsconfig.json ファイルを生成します。

Terminal window
npx tsc --init

tsconfig.json ファイルを更新します。

@@ -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. */

Jest の設定

次のコマンドを実行して Jest の設定を作成します。

  • “test” スクリプトに Jest を使用する: Yes
  • 設定に TypeScript を使用する: Yes
  • テスト環境: jsdom
  • カバレッジレポートを追加する: No
  • カバレッジのプロバイダー: v8
  • モック呼び出しを自動的にクリアする…: No
Terminal window
npm init jest@latest

生成された jest.config.ts を次のように編集します。

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

ESM のサポート

Node.js の実験的な VM モジュールを有効にするため、package.jsontest スクリプトを更新します。

"scripts": {
"test": "jest"
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
}

Jest が ESM として扱うファイルの拡張子を指定するため、jest.config.tsextensionsToTreatAsEsm を追加します。

extensionsToTreatAsEsm: ['.ts'],

ts-jest での ESM 設定については、公式ドキュメント を参照してください。

.js 拡張子を扱うため、jest.config.tsmoduleNameMapper を追加します。

// moduleNameMapper: {},
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},

ESM をサポートするように transform 設定を更新します。

// transform: undefined,
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
useESM: true,
},
],
},

ESM モジュールの作成

動作確認用に、hast-util-from-html パッケージをインストールします。

Terminal window
npm i hast-util-from-html

hast-util-from-html は ESM 専用のパッケージです。node_modules/hast-util-from-html/index.js を確認すると、ESM 構文でエクスポートされていることが分かります。

/**
* @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';

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

ESM のテスト

index.spec.ts を作成します。

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

テストを実行します。

Terminal window
npm run test

次のようなエラーが表示された場合、

● 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.

不足しているパッケージをインストールします。

Terminal window
npm i -D jest-environment-jsdom

再度テストを実行します。

Terminal window
npm run test

期待される出力は次の通りです。

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.

まとめ

package.json、TypeScript、Jest を連携させて設定すると、実際の ESM パッケージをインポートするテストが SyntaxError: Cannot use import statement outside a module で失敗せず、正常に通るようになりました。

このテストを通すには、package.jsontype フィールド、TypeScript のモジュール設定、Jest 自体の設定、実験的 VM モジュール用の Node.js 起動フラグという 4 つの設定をそろえる必要があります。type: module を追加するだけでは、元のエラーは解消しません。

moduleNameMapper の規則は、相対インポートの .js 接尾辞を取り除き、対応する TypeScript ソースを Jest が解決できるようにします。テスト環境における ESM 形式のインポート解決に固有の回避策なので、別のプロジェクトでも使う場合は、その目的を記録しておくとよいでしょう。

About the author

Takahiro Iwasa

Takahiro Iwasa

Software Developer

This blog shares technical notes from hands-on projects—architecture, implementation, and AWS service integrations.