Jest で ECMAScript Modules をテストする
package.json、TypeScript、Jest の設定を連携させ、Jest の "Cannot use import statement outside a module" エラーを解消する方法を紹介します。
Jest を使って ECMAScript Modules (ESM) をテストすると、次のエラーが発生することがあります。
SyntaxError: Cannot use import statement outside a module
これは、Jest が import を含むファイルを CommonJS として実行した場合に発生します。Jest は ESM の実験的サポート を提供していますが、Node.js、TypeScript、Jest の各設定で一貫して有効にする必要があります。
ESM パッケージの作成
まず、次のコマンドで新しい ESM パッケージを作成します。
mkdir jest-esm && cd jest-esmnpm init -y必要な開発用依存関係をインストールします。
npm i -D typescript jest @types/jest ts-node ts-jestpackage.json に "type": "module" を追加します。
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 ファイルを生成します。
npx tsc --inittsconfig.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
npm init jest@latest生成された jest.config.ts を次のように編集します。
// preset: undefined,preset: 'ts-jest',ESM のサポート
Node.js の実験的な VM モジュールを有効にするため、package.json の test スクリプトを更新します。
"scripts": { "test": "jest" "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"}Jest が ESM として扱うファイルの拡張子を指定するため、jest.config.ts に extensionsToTreatAsEsm を追加します。
extensionsToTreatAsEsm: ['.ts'],ts-jest での ESM 設定については、公式ドキュメント を参照してください。
.js 拡張子を扱うため、jest.config.ts に moduleNameMapper を追加します。
// moduleNameMapper: {},moduleNameMapper: { '^(\\.{1,2}/.*)\\.js$': '$1',},ESM をサポートするように transform 設定を更新します。
// transform: undefined,transform: { '^.+\\.tsx?$': [ 'ts-jest', { useESM: true, }, ],},ESM モジュールの作成
動作確認用に、hast-util-from-html パッケージをインストールします。
npm i hast-util-from-htmlhast-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 を作成します。
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 を作成します。
import JestEsm from './index';
test('case1', () => { JestEsm();});テストを実行します。
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.不足しているパッケージをインストールします。
npm i -D jest-environment-jsdom再度テストを実行します。
npm run test期待される出力は次の通りです。
PASS ./index.spec.ts ✓ case1 (20 ms)
Test Suites: 1 passed, 1 totalTests: 1 passed, 1 totalSnapshots: 0 totalTime: 1 sRan all test suites.まとめ
package.json、TypeScript、Jest を連携させて設定すると、実際の ESM パッケージをインポートするテストが SyntaxError: Cannot use import statement outside a module で失敗せず、正常に通るようになりました。
このテストを通すには、package.json の type フィールド、TypeScript のモジュール設定、Jest 自体の設定、実験的 VM モジュール用の Node.js 起動フラグという 4 つの設定をそろえる必要があります。type: module を追加するだけでは、元のエラーは解消しません。
moduleNameMapper の規則は、相対インポートの .js 接尾辞を取り除き、対応する TypeScript ソースを Jest が解決できるようにします。テスト環境における ESM 形式のインポート解決に固有の回避策なので、別のプロジェクトでも使う場合は、その目的を記録しておくとよいでしょう。
Related posts
Cognito User Pools と OIDC で Slack サインインを実装する
Cognito user pool を OIDC 経由で Slack と連携させ、"Sign in with Slack" を Amplify で Next.js アプリケーションに組み込みます。
Lambda Web Adapter で FastAPI を AWS Lambda にデプロイする
FastAPI で書いた API バックエンドをコンテナ化し、Lambda Web Adapter と AWS CDK を使って単一の Lambda 関数へデプロイします。
モジュール性と再利用性を高める React プロジェクト構成
React プロジェクトを components、core、features、shared に分割し、明確な依存境界を設けます。
モジュール性と再利用性を高める Angular のプロジェクト構成
Angular アプリケーションを core、features、shared に分け、依存関係の境界を明確にするフォルダ構成を紹介します。
Jasmine でモックオブジェクトのプロパティをスパイする
モックオブジェクトのプロパティをスパイする際に発生する Jasmine の "already been spied upon" エラーを、Object.getOwnPropertyDescriptor で回避する方法を解説します。
