T-CREATOR

Jest の “Cannot use import statement outside a module” を根治する手順

Jest の “Cannot use import statement outside a module” を根治する手順

Jest でテストを実行しようとした際に、「Cannot use import statement outside a module」というエラーに遭遇することがあります。このエラーは ES Modules(ESM)形式のコードを Jest が正しく解釈できていないために発生するものです。本記事では、このエラーの根本原因から実践的な解決策まで、段階的にわかりやすく解説していきます。

背景

JavaScript のモジュールシステムには、大きく分けて CommonJS と ES Modules(ESM)の 2 つの方式が存在します。

mermaidflowchart TB
    js["JavaScript<br/>モジュールシステム"]
    cjs["CommonJS<br/>(require/module.exports)"]
    esm["ES Modules<br/>(import/export)"]
    node["Node.js<br/>環境"]
    browser["ブラウザ<br/>環境"]

    js --> cjs
    js --> esm
    cjs -->|主に使用| node
    esm -->|主に使用| browser
    esm -->|サポート拡大| node

図の要点: JavaScript には CommonJS と ES Modules という 2 つのモジュール方式があり、それぞれ異なる環境で主に使われてきました。

CommonJS と ES Modules の違い

#項目CommonJSES Modules
1読み込み構文require()import
2出力構文module.exportsexport
3読み込みタイミング実行時(動的)パース時(静的)
4Node.js サポートデフォルト明示的設定が必要
5ブラウザサポート✕(バンドラー必須)○(ネイティブサポート)

Node.js は長らく CommonJS を標準としてきましたが、近年では ES Modules のサポートも進んでいます。Jest は Node.js 環境で動作するテストフレームワークであるため、デフォルトでは CommonJS 形式を期待しているのです。

Jest のデフォルト動作

Jest は以下のような動作フローでテストファイルを処理します。

mermaidflowchart LR
    test["テストファイル"]
    jest["Jest"]
    trans["変換処理<br/>(babel-jest)"]
    exec["実行環境<br/>(Node.js)"]
    result["テスト結果"]

    test -->|読み込み| jest
    jest -->|変換| trans
    trans -->|CommonJS化| exec
    exec -->|実行| result

この変換処理が正しく設定されていない場合、ES Modules 構文がそのまま Node.js に渡されてしまい、エラーが発生してしまいます。

課題

「Cannot use import statement outside a module」エラーが発生する主な原因を整理しましょう。

エラーの発生パターン

このエラーは以下のような状況で発生します。

エラーコード: SyntaxError: Cannot use import statement outside a module

エラーメッセージ例:

swiftSyntaxError: Cannot use import statement outside a module
    at Object.compileFunction (node:vm:352:18)
    at wrapSafe (node:internal/modules/cjs/loader:1033:15)
    at Module._compile (node:internal/modules/cjs/loader:1069:27)

発生条件:

  • ES Modules 構文(import/export)を使用したファイルをテストしようとしている
  • Jest の設定で ES Modules を適切に処理する設定がされていない
  • package.json"type": "module" が設定されている、または .mjs 拡張子を使用している
  • 外部ライブラリが ES Modules のみで提供されている

根本的な問題

以下の図は、エラーが発生するメカニズムを示しています。

mermaidflowchart TD
    code["ESM 構文の<br/>コード"]
    jest["Jest"]
    check{{"変換設定<br/>あり?"}}
    transform["Babel/SWC<br/>による変換"]
    cjs["CommonJS<br/>形式"]
    direct["ESM 構文<br/>そのまま"]
    node["Node.js<br/>実行環境"]
    success["✓ テスト成功"]
    error["✕ エラー発生"]

    code --> jest
    jest --> check
    check -->|Yes| transform
    check -->|No| direct
    transform --> cjs
    cjs --> node
    direct --> node
    node --> success
    node --> error

図で理解できる要点:

  • Jest が ES Modules を CommonJS に変換する設定がない場合、エラーが発生する
  • 適切な変換処理を設定することで、ES Modules のコードも Jest で実行できる
  • 変換ツールには Babel や SWC などの選択肢がある

解決策

このエラーを根治するには、複数のアプローチがあります。プロジェクトの状況に応じて最適な方法を選択しましょう。

解決方法の選択フロー

mermaidflowchart TD
    start["エラー発生"]
    q1{{"TypeScript<br/>使用?"}}
    q2{{"Next.js など<br/>フレームワーク?"}}
    q3{{"最新の<br/>環境?"}}

    sol1["① ts-jest を使用"]
    sol2["② フレームワークの<br/>Jest 設定を使用"]
    sol3["③ babel-jest で<br/>変換設定"]
    sol4["④ Experimental<br/>ESM サポート"]

    start --> q1
    q1 -->|Yes| sol1
    q1 -->|No| q2
    q2 -->|Yes| sol2
    q2 -->|No| q3
    q3 -->|Yes| sol4
    q3 -->|No| sol3

それぞれの解決方法を詳しく見ていきましょう。

① TypeScript プロジェクトの場合: ts-jest を使用

TypeScript を使用しているプロジェクトでは、ts-jest が最も推奨される解決策です。

必要なパッケージのインストール:

bashyarn add -D jest ts-jest @types/jest

Jest 設定の初期化:

bashyarn ts-jest config:init

このコマンドにより、jest.config.js ファイルが自動生成されます。

生成される jest.config.js の内容:

javascript/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
  // ts-jest をプリセットとして使用
  preset: 'ts-jest',

  // Node.js 環境でテストを実行
  testEnvironment: 'node',
};

この設定により、Jest は TypeScript ファイルを自動的に変換して実行できるようになります。

より詳細な設定が必要な場合:

javascript/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',

  // TypeScript の設定を明示的に指定
  transform: {
    '^.+\\.tsx?$': [
      'ts-jest',
      {
        tsconfig: {
          // ESM を有効化
          esModuleInterop: true,
          allowSyntheticDefaultImports: true,
        },
      },
    ],
  },
};

この設定では、transform オプションで TypeScript ファイル(.ts.tsx)の変換方法を詳細に指定しています。

tsconfig.json の設定確認:

json{
  "compilerOptions": {
    "module": "ESNext",
    "target": "ES2020",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "moduleResolution": "node"
  }
}

これらの設定により、TypeScript プロジェクトで ES Modules を使用しながら、Jest でのテストも問題なく実行できるようになります。

② Babel を使用した変換設定

TypeScript を使用していない JavaScript プロジェクトや、既に Babel を使用しているプロジェクトでは、babel-jest を活用します。

必要なパッケージのインストール:

bashyarn add -D jest babel-jest @babel/core @babel/preset-env

Babel 設定ファイルの作成(babel.config.js):

javascriptmodule.exports = {
  presets: [
    // Node.js の現在のバージョンに合わせて変換
    [
      '@babel/preset-env',
      {
        targets: {
          node: 'current',
        },
      },
    ],
  ],
};

この設定により、ES Modules 構文が CommonJS に自動変換されます。

React を使用している場合の Babel 設定:

javascriptmodule.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: {
          node: 'current',
        },
      },
    ],
    // React の JSX 構文にも対応
    '@babel/preset-react',
  ],
};

Jest 設定ファイル(jest.config.js):

javascriptmodule.exports = {
  // テスト環境の指定
  testEnvironment: 'node',

  // Babel による変換を有効化
  transform: {
    '^.+\\.(js|jsx)$': 'babel-jest',
  },

  // 変換対象外のモジュールを指定
  transformIgnorePatterns: [
    'node_modules/(?!(module-to-transform)/)',
  ],
};

transformIgnorePatterns は、通常 node_modules 内のファイルは変換されませんが、ES Modules のみで提供されているパッケージがある場合に使用します。

③ 外部パッケージが ESM のみの場合の対処

最近の npm パッケージの中には、ES Modules 形式でのみ提供されているものがあります。この場合、特別な設定が必要です。

問題となるパッケージの例:

  • node-fetch v3 以降
  • chalk v5 以降
  • got v12 以降

Jest 設定での対処方法:

javascriptmodule.exports = {
  testEnvironment: 'node',

  // ESM パッケージを変換対象に含める
  transformIgnorePatterns: [
    'node_modules/(?!(node-fetch|chalk|got)/)',
  ],

  transform: {
    '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
  },
};

この設定により、指定したパッケージも Babel で変換されるようになります。

複数のパッケージを指定する場合:

javascriptconst esModules = [
  'node-fetch',
  'chalk',
  'got',
  'p-limit',
  'yocto-queue',
].join('|');

module.exports = {
  transformIgnorePatterns: [
    `node_modules/(?!(${esModules})/)`,
  ],
};

配列で管理することで、後からパッケージを追加しやすくなります。

④ Jest の Experimental ESM サポートを使用

Jest 28 以降では、実験的に ES Modules のネイティブサポートが提供されています。ただし、これは実験的機能であることに注意が必要です。

package.json の設定:

json{
  "type": "module"
}

jest.config.js の作成(拡張子を .mjs に変更):

javascript// jest.config.mjs として保存
export default {
  testEnvironment: 'node',

  // 拡張子の明示的な指定
  extensionsToTreatAsEsm: ['.ts', '.tsx'],

  // モジュール名のマッピング
  moduleNameMapper: {
    '^(\\.{1,2}/.*)\\.js$': '$1',
  },

  // TypeScript 使用時の設定
  transform: {
    '^.+\\.tsx?$': [
      'ts-jest',
      {
        useESM: true,
      },
    ],
  },
};

テスト実行コマンド:

bashnode --experimental-vm-modules node_modules/jest/bin/jest.js

package.json のスクリプト設定:

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

この方法は最も新しいアプローチですが、実験的機能であるため、本番プロジェクトでの使用は慎重に検討してください。

具体例

実際のプロジェクトでエラーが発生し、解決するまでの流れを見ていきましょう。

エラーが発生するコード例

テスト対象のコード(src/utils/calculator.js):

javascript// ES Modules 構文を使用
export const add = (a, b) => {
  return a + b;
};
javascriptexport const subtract = (a, b) => {
  return a - b;
};
javascriptexport const multiply = (a, b) => {
  return a * b;
};

各関数をモジュールとして export しています。

テストコード(src/utils/calculator.test.js):

javascript// ES Modules 形式でインポート
import { add, subtract, multiply } from './calculator.js';
javascript// Jest のテストケース
describe('Calculator functions', () => {
  test('add function should return sum of two numbers', () => {
    expect(add(2, 3)).toBe(5);
  });
});
javascriptdescribe('Calculator functions', () => {
  test('subtract function should return difference', () => {
    expect(subtract(5, 3)).toBe(2);
  });

  test('multiply function should return product', () => {
    expect(multiply(3, 4)).toBe(12);
  });
});

エラーの発生

テスト実行:

bashyarn jest

エラー出力:

javascriptFAIL  src/utils/calculator.test.jsTest suite failed to run

    Jest encountered an unexpected token

    SyntaxError: Cannot use import statement outside a module

      1 | import { add, subtract, multiply } from './calculator.js';
        | ^^^^^^

    at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1350:14)

このエラーメッセージから、Jest が import 文を理解できていないことがわかります。

解決手順(Babel を使用する場合)

手順 1: 必要なパッケージのインストール:

bashyarn add -D babel-jest @babel/core @babel/preset-env

手順 2: Babel 設定ファイルの作成:

babel.config.js を作成します。

javascriptmodule.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: {
          node: 'current',
        },
      },
    ],
  ],
};

この設定により、現在の Node.js バージョンに合わせてコードが変換されます。

手順 3: Jest 設定ファイルの作成:

jest.config.js を作成します。

javascriptmodule.exports = {
  testEnvironment: 'node',

  // Babel による変換を有効化
  transform: {
    '^.+\\.js$': 'babel-jest',
  },
};

手順 4: テストの再実行:

bashyarn jest

成功時の出力:

bashPASS  src/utils/calculator.test.js
  Calculator functions
    ✓ add function should return sum of two numbers (3 ms)
    ✓ subtract function should return difference (1 ms)
    ✓ multiply function should return product (1 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total

これで、ES Modules 構文を使用したコードが Jest で正しくテストできるようになりました。

TypeScript プロジェクトでの具体例

テスト対象のコード(src/services/userService.ts):

typescript// TypeScript の型定義
export interface User {
  id: number;
  name: string;
  email: string;
}
typescript// ユーザーを作成する関数
export const createUser = (
  name: string,
  email: string
): User => {
  return {
    id: Math.floor(Math.random() * 1000),
    name,
    email,
  };
};
typescript// ユーザーの妥当性を検証する関数
export const validateUser = (user: User): boolean => {
  return user.name.length > 0 && user.email.includes('@');
};

テストコード(src/services/userService.test.ts):

typescriptimport {
  createUser,
  validateUser,
  User,
} from './userService';
typescriptdescribe('User Service', () => {
  test('createUser should create a valid user object', () => {
    const user = createUser('太郎', 'taro@example.com');

    expect(user).toHaveProperty('id');
    expect(user.name).toBe('太郎');
    expect(user.email).toBe('taro@example.com');
  });
});
typescriptdescribe('User Service', () => {
  test('validateUser should return true for valid user', () => {
    const validUser: User = {
      id: 1,
      name: '花子',
      email: 'hanako@example.com',
    };

    expect(validateUser(validUser)).toBe(true);
  });

  test('validateUser should return false for invalid user', () => {
    const invalidUser: User = {
      id: 2,
      name: '',
      email: 'invalid-email',
    };

    expect(validateUser(invalidUser)).toBe(false);
  });
});

解決手順(ts-jest を使用):

手順 1: パッケージのインストール:

bashyarn add -D jest ts-jest @types/jest typescript

手順 2: Jest 設定の初期化:

bashyarn ts-jest config:init

手順 3: 生成された jest.config.js の確認:

javascript/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};

手順 4: tsconfig.json の設定確認:

json{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020"],
    "esModuleInterop": true,
    "moduleResolution": "node",
    "strict": true
  },
  "include": ["src/**/*"]
}

手順 5: テストの実行:

bashyarn jest

成功時の出力:

sqlPASS  src/services/userService.test.ts
  User Service
    ✓ createUser should create a valid user object (4 ms)
    ✓ validateUser should return true for valid user (1 ms)
    ✓ validateUser should return false for invalid user (1 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total

TypeScript プロジェクトでも、適切な設定により ES Modules をスムーズに使用できます。

トラブルシューティング: よくある追加のエラー

エラー 1: Cannot find module エラーが発生する場合

javascriptError: Cannot find module './calculator.js' from 'calculator.test.js'

解決方法: moduleNameMapper を設定します。

javascriptmodule.exports = {
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
  },
};

エラー 2: regeneratorRuntime is not defined エラーが発生する場合

vbnetReferenceError: regeneratorRuntime is not defined

解決方法: @babel​/​plugin-transform-runtime を追加します。

bashyarn add -D @babel/plugin-transform-runtime
javascript// babel.config.js
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: { node: 'current' },
      },
    ],
  ],
  plugins: ['@babel/plugin-transform-runtime'],
};

これらの設定により、async/await を使用したコードも正しく動作します。

まとめ

「Cannot use import statement outside a module」エラーは、Jest が ES Modules 構文を適切に処理できていないことが原因で発生します。しかし、本記事で紹介した設定を行うことで、このエラーを根本から解決できるのです。

解決方法は主に以下の 4 つがあります。

#方法対象プロジェクト推奨度
1ts-jest を使用TypeScript プロジェクト★★★
2babel-jest を使用JavaScript プロジェクト★★★
3transformIgnorePatterns の設定ESM パッケージを使用★★☆
4Experimental ESM サポート最新環境・実験的★☆☆

重要なポイント:

  • プロジェクトの技術スタックに応じて適切な解決方法を選択しましょう
  • TypeScript を使用している場合は ts-jest が最も確実で推奨されます
  • 外部パッケージが ESM のみの場合は transformIgnorePatterns の設定が必要になります
  • Babel を使用する場合は、babel.config.jsjest.config.js の両方を正しく設定してください

これらの設定を適切に行うことで、モダンな ES Modules 構文を使用しながら、Jest による快適なテスト環境を構築できます。エラーメッセージに惑わされず、プロジェクトの状況に合わせた最適な解決策を選択することが、効率的な開発につながるでしょう。

関連リンク