できない.dev

Jest で path alias(@/)が解決できず Cannot find module になる

tsconfig.json の paths や bundler の alias は Jest には伝わらない。
Jest は独自の解決器を使うため、jest.config の moduleNameMapper に同じ対応を書き写す必要がある。
正規表現の後方参照と rootDir トークンで対応付ける。

公開: 更新:

要約

アプリのビルドは通るのにテストだけ Cannot find module '@/lib/foo' from 'src/app.test.ts' になるのは、@/ の対応付けが Jest に届いていないためである。

tsconfig.jsonpaths は TypeScript の型解決のための設定で、実行時のモジュール解決までは面倒を見ない。
webpack や Vite の resolve.alias も同様に、そのバンドラの中でしか効かない。
Jest は独自の解決器を持つので、Jest の設定(新しいタブで開く)側にも同じ対応を書き写す必要がある。

Cannot find module '@/lib/foo' from 'src/app.test.ts'

よくある原因

  1. tsconfig だけ書いている: paths は型チェックのための宣言である。tsc は納得するが、Jest の実行時解決は別経路なので届かない。
  2. バンドラの alias に頼っている: resolve.alias はそのバンドラのビルド時にしか効かない。
    Jest はバンドルを経由せずファイルを読むため無関係である。
  3. パターンが緩い: '@/(.*)' のように境界を付けずに書くと、@scope/pkg のような別モジュールにも当たって解決先が壊れる。
    公式ドキュメントも境界の無い指定を避けるよう注意している。
  4. 相対パスで書いた: 置換先を ./src/$1 のように書くと、rootDir ではなく解決の基点次第でずれる。
  5. 拡張子が対象外: moduleFileExtensions に無い拡張子は、対応付けが正しくても見つからない。

解決策

1. moduleNameMapper に写す

// jest.config.js
module.exports = {
  moduleNameMapper: {
    "^@/(.*)$": "<rootDir>/src/$1",
    "^~/components/(.*)$": "<rootDir>/src/components/$1",
  },
};

tsconfig.json"@/*": ["src/*"] に 1 対 1 で対応させる。$1 は正規表現の後方参照で、<rootDir> は Jest が設定ファイルの位置から決めるプロジェクトルートである。

2. 境界を付ける

// NG: @scope/pkg にも当たる
"@/(.*)": "<rootDir>/src/$1",
 
// OK: 先頭が @/ のときだけ
"^@/(.*)$": "<rootDir>/src/$1",

対応付けは上から順に評価され、最初に一致したものが使われる。
広いパターンを先に置かない。

3. スタイルや画像はスタブへ

moduleNameMapper: {
  "^@/(.*)$": "<rootDir>/src/$1",
  "\\.(css|scss)$": "<rootDir>/test/styleStub.js",
  "\\.(png|jpg|svg)$": "<rootDir>/test/fileStub.js",
},

CSS を読み込めずに落ちているのか、エイリアスが解けていないのかを切り分けやすくなる。

4. 実際に効いている設定を見る

npx jest --showConfig | grep -A 10 moduleNameMapper

設定ファイルが複数ある構成では、意図した方が読まれていないことがある。
まず効いている値を確認してから直す。

この記事は役立ちましたか?