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.json の paths は TypeScript の型解決のための設定で、実行時のモジュール解決までは面倒を見ない。
webpack や Vite の resolve.alias も同様に、そのバンドラの中でしか効かない。
Jest は独自の解決器を持つので、Jest の設定(新しいタブで開く)側にも同じ対応を書き写す必要がある。
Cannot find module '@/lib/foo' from 'src/app.test.ts'よくある原因
- tsconfig だけ書いている:
pathsは型チェックのための宣言である。tscは納得するが、Jest の実行時解決は別経路なので届かない。 - バンドラの alias に頼っている:
resolve.aliasはそのバンドラのビルド時にしか効かない。
Jest はバンドルを経由せずファイルを読むため無関係である。 - パターンが緩い:
'@/(.*)'のように境界を付けずに書くと、@scope/pkgのような別モジュールにも当たって解決先が壊れる。
公式ドキュメントも境界の無い指定を避けるよう注意している。 - 相対パスで書いた: 置換先を
./src/$1のように書くと、rootDirではなく解決の基点次第でずれる。 - 拡張子が対象外:
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設定ファイルが複数ある構成では、意図した方が読まれていないことがある。
まず効いている値を確認してから直す。