ts-jestが外部ライブラリをimportできない場合の対処方

ブログ
TypeScript
ユニットテスト
約1500字

あるnpmパッケージを追加した後、Webpackのビルドは通っているのにJestでは以下のようなエラーが出るようになった。

  ● Test suite failed to run

    Jest encountered an unexpected token

    This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.

    By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".

    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/en/ecmascript-modules for how to enable it.
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/en/configuration.html

    Details:

    /プロジェクトディレクトリ/node_modules/なにかのパッケージ/index.js:7

    export * from './Hoge';
    ^^^^^^

    SyntaxError: Unexpected token 'export'

原因

  • 外部ライブラリはESModuleで提供されていた
    • いわゆる import/export できるやつ
    • JestはNode上でテストを実行するため、Webpackでビルドする場合とはESModuleの扱い方が違う
  • TypeScript案件だったのでJestにts-jestしか構成していなかったが、ts-jestはESModuleに対応していない
  • このため、node_modules内にESModuleで提供されているパッケージがあると、そのままではJest上ではimportできない
  • Jest上でESModuleをimportするにはどうしてもbabel-jestが必要

対処方法

babel関係のパッケージを追加

npm i -D babel-jest @babel/preset-env @babel/plugin-transform-modules-commonjs

@babel/corebabel-jestに含まれるため不要だった

babel.config.jsを追加

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: {
          node: 'current',
        },
      }
    ]
  ],
  plugins: [
    '@babel/plugin-transform-modules-commonjs',
  ]
}

このとき設定ファイルを.babelrcとして作成してしまうと意図通りに動作しないのでbabel.config.jsとすること。 .babelrcで設定ファイルを作るとデフォルトではnode_modules内のファイルには設定が適用されない。

For people compiling node_modules this is also a change, because a .babelrc in one package will not affect that package's node_modules. You'll also want to rename your project-root .babelrc to a babel.config.js file exporting the config, to do that.
Only in coverage mode, latest babel: ‘import’ and ‘export’ may appear only with ‘sourceType: “module”’ · Issue #6053 · facebook/jest

どうやらbabel7系からデフォルトでnode_modulesは除外するようになったようで、webpack側で設定してもbabelの方で除外されてしまったようです。
babelがnode_modulesを通してくれないときの対応 - Qiita

Jestの設定を修正

transformtransformIgnorePatternsの2箇所を修正する。

transformbabel-jestのパターンを追加

"transform": {
  "\\.jsx?$": "babel-jest",
  "\\.tsx?$": "ts-jest"
}

ESModuleで提供されているパッケージをtransformIgnorePatternsに記載

transformIgnorePatternsはtransformしない対象を指定するオプションで、デフォルトではnode_moduels全体が指定されている。これを上書きしてESModuleのパッケージだけをtransoformさせたいので、否定先読みを使って「ESModuleのパッケージ以外のnode_modules」を意味する記述となるようにする。やりたいことをNOT表現にしないといけないのでややこしい。

"transformIgnorePatterns": [
  "/node_modules/(?!なにかのパッケージ|ESMが別パッケージのESMを呼んだりしているときは依存先も書く).+\\.js",
]

次期バージョンのts-jestではESMに対応予定

次期バージョンではts-jest単体でESModuleが使えるようになるらしい。

ts-jest supports ESM via a config option useESM in combination with jest config option extensionsToTreatAsEsm.
ESM Support | ts-jest

※投稿内容は私個人の意見であり、所属企業・部門見解を代表するものではありません。