Jest moduleNameMapper 早見表:パスエイリアス/静的アセット/CSS を一網打尽
Jest でテストを書いていると、パスエイリアスや静的ファイルのインポートでエラーに遭遇することがよくあります。そんなとき頼りになるのが moduleNameMapper 設定です。この記事では、実務で即使える設定パターンを早見表とともにご紹介します。
moduleNameMapper 設定早見表
以下の表は、よく使われる moduleNameMapper の設定パターンをまとめたものです。プロジェクトに応じて参考にしてください。
| # | 用途 | 設定キー(正規表現) | マッピング先 | 備考 |
|---|---|---|---|---|
| 1 | パスエイリアス @/ | ^@/(.*)$ | <rootDir>/src/$1 | TypeScript の paths と対応 |
| 2 | パスエイリアス ~/ | ^~/(.*)$ | <rootDir>/$1 | プロジェクトルートを指す |
| 3 | CSS ファイル | \\.css$ | identity-obj-proxy | CSS Modules のモック |
| 4 | CSS Modules | \\.module\\.css$ | identity-obj-proxy | className を文字列として返す |
| 5 | SCSS/Sass | \\.(scss|sass)$ | identity-obj-proxy | CSS と同様にモック |
| 6 | 画像ファイル | \\.(jpg|jpeg|png|gif|svg)$ | <rootDir>/__mocks__/fileMock.js | 静的アセットのモック |
| 7 | フォント | \\.(woff|woff2|eot|ttf|otf)$ | <rootDir>/__mocks__/fileMock.js | フォントファイルのモック |
| 8 | 静的ファイル全般 | \\.(css|less|scss|sass|styl)$ | identity-obj-proxy | スタイル系を一括モック |
| 9 | node_modules 特定パッケージ | ^lodash-es$ | lodash | ESM を CommonJS に置換 |
| 10 | JSON ファイル | \\.json$ | <rootDir>/__mocks__/jsonMock.js | 必要に応じてモック |
背景
Jest とモジュール解決の課題
Jest は Node.js 環境でテストを実行するため、ブラウザ専用の機能や Webpack、Vite などのバンドラー固有の機能をそのままでは理解できません。
現代のフロントエンド開発では、以下のような便利な機能が当たり前に使われています。
- パスエイリアス:
import Button from '@/components/Button'のような短縮記法 - CSS Modules:
import styles from './Button.module.css'でスタイルをインポート - 静的アセット:
import logo from './logo.png'で画像パスを取得 - ESM パッケージ:
lodash-esのような ES Modules 形式のライブラリ
しかし、Jest はこれらをデフォルトでは解決できないため、テスト実行時にエラーが発生してしまいます。
moduleNameMapper の役割
moduleNameMapper は、モジュールのインポートパスを Jest が理解できる形式に変換するための設定です。正規表現でパターンマッチし、適切なモックファイルや実際のパスに置き換えることで、テストを正常に動作させます。
以下の図は、moduleNameMapper がどのようにインポート文を変換するかを示しています。
mermaidflowchart LR
original["ソースコード<br/>import '@/utils/helper'"] -->|Jest 実行| mapper["moduleNameMapper"]
mapper -->|正規表現マッチ| pattern["^@/(.*)$"]
pattern -->|変換| resolved["<rootDir>/src/utils/helper"]
resolved --> test["テスト実行成功"]
図で理解できる要点:
- インポート文が正規表現でマッチングされる
- マッピング設定に従って実際のパスに変換される
- Jest がモジュールを正しく解決できるようになる
課題
よくあるエラーとその原因
moduleNameMapper の設定不足や誤りにより、以下のようなエラーが発生します。
エラー 1: パスエイリアスが解決できない
エラーコード: Cannot find module '@/components/Button' from 'App.test.tsx'
typescript// App.test.tsx
import { Button } from '@/components/Button'; // ❌ Jest が '@/' を理解できない
発生条件:
- TypeScript の
tsconfig.jsonでパスエイリアスを設定している - Jest 側で対応する
moduleNameMapperが未設定
エラー 2: CSS Modules のインポートエラー
エラーコード: Jest encountered an unexpected token
cssSyntaxError: Unexpected token '.'
.button {
^
発生条件:
- CSS ファイルを直接インポートしている
- Jest が CSS の構文を解析しようとしてしまう
typescript// Button.tsx
import styles from './Button.module.css'; // ❌ Jest が CSS を JavaScript として解析
エラー 3: 画像ファイルのインポートエラー
エラーコード: SyntaxError: Invalid or unexpected token
typescript// Logo.tsx
import logo from './logo.png'; // ❌ Jest がバイナリファイルを解析できない
発生条件:
- 静的アセットをモジュールとしてインポートしている
- 画像ファイルのモックが未設定
以下の図は、これらのエラーがどのように発生するかを示しています。
mermaidflowchart TD
import["ソースコード内の import 文"] --> check{"moduleNameMapper<br/>にマッチ?"}
check -->|Yes| mock["モック/変換後のパス"]
check -->|No| error["❌ エラー発生"]
mock --> testok["✓ テスト実行"]
error --> error1["Cannot find module"]
error --> error2["Unexpected token"]
error --> error3["Invalid token"]
図で理解できる要点:
moduleNameMapperにマッチしないとエラーが発生- 適切な設定がテスト成功の鍵となる
解決策
基本的な設定構造
jest.config.js または jest.config.ts に moduleNameMapper を記述します。
javascript// jest.config.js の基本構造
module.exports = {
moduleNameMapper: {
// キー: 正規表現パターン
// 値: 変換先のパスまたはモジュール
'^@/(.*)$': '<rootDir>/src/$1',
},
};
設定のポイント:
- キーは正規表現の文字列(バックスラッシュのエスケープに注意)
<rootDir>は Jest のルートディレクトリを指す特殊変数$1はキャプチャグループ(括弧内)にマッチした部分
パスエイリアスの設定
TypeScript や Webpack で設定したパスエイリアスを Jest に認識させます。
tsconfig.json の設定例
json{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"~/*": ["./*"]
}
}
}
この設定に対応する moduleNameMapper は以下のようになります。
jest.config.js での対応設定
javascriptmodule.exports = {
moduleNameMapper: {
// '@/' を 'src/' に変換
'^@/(.*)$': '<rootDir>/src/$1',
// '~/' をプロジェクトルートに変換
'^~/(.*)$': '<rootDir>/$1',
},
};
設定の読み方:
^@/(.*)$:@/で始まり、その後に任意の文字列が続くパターン<rootDir>/src/$1:@/を<rootDir>/src/に置き換え、$1に残りの部分を適用
CSS と CSS Modules の設定
スタイルファイルのインポートをモックに置き換えます。
CSS Modules 用のモック設定
javascriptmodule.exports = {
moduleNameMapper: {
// CSS Modules(.module.css)を identity-obj-proxy でモック
'\\.module\\.(css|scss|sass)$': 'identity-obj-proxy',
// 通常の CSS ファイルもモック
'\\.(css|scss|sass|less|styl)$': 'identity-obj-proxy',
},
};
identity-obj-proxy とは:
- CSS Modules の className をそのまま文字列として返すモックライブラリ
styles.button→"button"のように変換されるため、テストでクラス名の検証が可能
インストール方法
bashyarn add -D identity-obj-proxy
テストでの使用例
typescript// Button.test.tsx
import { render } from '@testing-library/react';
import Button from './Button';
test('ボタンに正しいクラス名が適用されている', () => {
const { container } = render(<Button />);
const button = container.querySelector('.button');
// identity-obj-proxy により、className が文字列として取得可能
expect(button).toBeInTheDocument();
});
静的アセット(画像・フォント)の設定
画像やフォントファイルをモックファイルで置き換えます。
モックファイルの作成
まず、静的ファイル用のモックを作成します。
javascript// __mocks__/fileMock.js
module.exports = 'test-file-stub';
このシンプルなモックは、すべての静的ファイルを文字列 'test-file-stub' として扱います。
jest.config.js での設定
javascriptmodule.exports = {
moduleNameMapper: {
// 画像ファイルをモックに置換
'\\.(jpg|jpeg|png|gif|svg|webp)$':
'<rootDir>/__mocks__/fileMock.js',
// フォントファイルをモックに置換
'\\.(woff|woff2|eot|ttf|otf)$':
'<rootDir>/__mocks__/fileMock.js',
},
};
正規表現の解説:
\\.: ドット(.)をエスケープ(jpg|jpeg|png|...): OR 条件で複数の拡張子にマッチ$: 文字列の終端
テストでの検証例
typescript// Logo.test.tsx
import logo from './logo.png';
test('ロゴのパスが取得できる', () => {
// logo は 'test-file-stub' という文字列になる
expect(logo).toBe('test-file-stub');
});
ESM パッケージの対応
一部の npm パッケージは ES Modules(ESM)形式でのみ提供されていますが、Jest は CommonJS を前提としているため、そのままでは動作しません。
よくある ESM パッケージの例
lodash-esnanoiduuid(v9 以降)
エラー例
エラーコード: SyntaxError: Cannot use import statement outside a module
javascriptexport { default as debounce } from './debounce.js';
^^^^^^
SyntaxError: Cannot use import statement outside a module
moduleNameMapper での解決方法
javascriptmodule.exports = {
moduleNameMapper: {
// ESM 版を CommonJS 版にマッピング
'^lodash-es$': 'lodash',
// nanoid の ESM を CommonJS に置換
'^nanoid$': require.resolve('nanoid'),
},
};
別の解決方法:
transformIgnorePatterns を使って ESM パッケージを変換対象に含める方法もあります。
javascriptmodule.exports = {
transformIgnorePatterns: [
'node_modules/(?!(lodash-es|nanoid)/)',
],
};
具体例
Next.js プロジェクトの実践的な設定
Next.js では、パスエイリアス、CSS Modules、画像最適化など、複数の機能が組み合わさっています。それらすべてに対応した設定例をご紹介します。
プロジェクト構成
cssmy-nextjs-app/
├── src/
│ ├── components/
│ │ ├── Button/
│ │ │ ├── Button.tsx
│ │ │ ├── Button.module.css
│ │ │ └── Button.test.tsx
│ │ └── Header/
│ │ ├── Header.tsx
│ │ └── logo.svg
│ ├── utils/
│ │ └── helper.ts
│ └── app/
│ └── page.tsx
├── __mocks__/
│ └── fileMock.js
├── jest.config.js
├── tsconfig.json
└── package.json
tsconfig.json の設定
json{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@components/*": ["./src/components/*"],
"@utils/*": ["./src/utils/*"]
}
}
}
この設定により、ソースコード内で以下のようなインポートが可能になります。
typescript// パスエイリアスを使ったインポート例
import { Button } from '@/components/Button/Button';
import { formatDate } from '@utils/helper';
完全な jest.config.js
以下は、上記のプロジェクト構成に対応した jest.config.js の完全版です。
javascriptmodule.exports = {
// テスト環境の指定
testEnvironment: 'jsdom',
// ルートディレクトリ
roots: ['<rootDir>/src'],
// モジュール名のマッピング(優先順位が重要)
moduleNameMapper: {
// 1. パスエイリアス(具体的なものから先に記述)
'^@components/(.*)$': '<rootDir>/src/components/$1',
'^@utils/(.*)$': '<rootDir>/src/utils/$1',
'^@/(.*)$': '<rootDir>/src/$1',
// 2. CSS Modules(.module.css を先にマッチさせる)
'\\.module\\.(css|scss|sass)$': 'identity-obj-proxy',
// 3. 通常の CSS ファイル
'\\.(css|scss|sass|less)$': 'identity-obj-proxy',
// 4. 画像ファイル
'\\.(jpg|jpeg|png|gif|svg|webp|ico)$':
'<rootDir>/__mocks__/fileMock.js',
// 5. フォントファイル
'\\.(woff|woff2|eot|ttf|otf)$':
'<rootDir>/__mocks__/fileMock.js',
},
// ファイル拡張子の解決順序
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
// 変換対象外のパターン
transformIgnorePatterns: [
'node_modules/(?!(nanoid|lodash-es)/)',
],
// テストファイルのパターン
testMatch: [
'**/__tests__/**/*.[jt]s?(x)',
'**/?(*.)+(spec|test).[jt]s?(x)',
],
};
設定のポイント:
moduleNameMapperの記述順序が重要(より具体的なパターンを先に)- CSS Modules の
.module.cssを通常の.cssより先に記述 - ESM パッケージは
transformIgnorePatternsで変換対象に含める
mocks/fileMock.js の内容
javascript// __mocks__/fileMock.js
// 静的アセットのモック(画像、フォントなど)
module.exports = 'test-file-stub';
コンポーネントの実装例
typescript// src/components/Button/Button.tsx
import styles from './Button.module.css';
interface ButtonProps {
label: string;
onClick?: () => void;
}
export const Button: React.FC<ButtonProps> = ({
label,
onClick,
}) => {
return (
<button className={styles.button} onClick={onClick}>
{label}
</button>
);
};
このコンポーネントに対するテストを書いてみます。
テストコードの実装例
typescript// src/components/Button/Button.test.tsx
import {
render,
screen,
fireEvent,
} from '@testing-library/react';
import { Button } from './Button';
describe('Button コンポーネント', () => {
test('ラベルが正しく表示される', () => {
render(<Button label='送信' />);
// ボタンのテキストを検証
expect(screen.getByText('送信')).toBeInTheDocument();
});
test('クリックイベントが発火する', () => {
const handleClick = jest.fn();
render(
<Button label='クリック' onClick={handleClick} />
);
// ボタンをクリック
fireEvent.click(screen.getByText('クリック'));
// ハンドラーが呼ばれたことを検証
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
このテストは、moduleNameMapper により CSS Modules が適切にモックされているため、エラーなく実行されます。
TypeScript + Vite プロジェクトの設定例
Vite を使用したプロジェクトでも、同様に moduleNameMapper の設定が必要です。
vite.config.ts でのエイリアス設定
typescript// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@assets': path.resolve(__dirname, './src/assets'),
},
},
});
この Vite の設定に対応する Jest の設定は以下のようになります。
対応する jest.config.ts
typescript// jest.config.ts
import type { Config } from 'jest';
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
moduleNameMapper: {
// Vite のエイリアスと同じ設定
'^@/(.*)$': '<rootDir>/src/$1',
'^@assets/(.*)$': '<rootDir>/src/assets/$1',
// スタイルファイル
'\\.module\\.(css|scss)$': 'identity-obj-proxy',
'\\.(css|scss)$': 'identity-obj-proxy',
// 静的アセット
'\\.(png|jpg|jpeg|gif|svg|webp)$':
'<rootDir>/__mocks__/fileMock.js',
},
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
};
export default config;
画像インポートを含むコンポーネント例
typescript// src/components/Header/Header.tsx
import logo from '@assets/logo.svg';
import styles from './Header.module.css';
export const Header: React.FC = () => {
return (
<header className={styles.header}>
<img src={logo} alt='ロゴ' className={styles.logo} />
<h1>My App</h1>
</header>
);
};
このコンポーネントのテストでは、画像ファイルが適切にモックされます。
typescript// src/components/Header/Header.test.tsx
import { render, screen } from '@testing-library/react';
import { Header } from './Header';
test('ヘッダーにロゴが表示される', () => {
render(<Header />);
const logo = screen.getByAltText('ロゴ');
// モックされた画像パスが設定されている
expect(logo).toHaveAttribute('src', 'test-file-stub');
});
複雑なケース: SVG を React コンポーネントとして扱う
Next.js や Vite では、SVG を React コンポーネントとしてインポートできます。
typescript// ソースコード内での SVG インポート
import { ReactComponent as Logo } from './logo.svg';
// 使用例
<Logo width={100} height={100} />;
この場合、moduleNameMapper での対応が少し複雑になります。
SVG コンポーネント用のモック作成
javascript// __mocks__/svgMock.js
import React from 'react';
const SvgMock = React.forwardRef((props, ref) => (
<svg ref={ref} {...props} />
));
SvgMock.displayName = 'SvgMock';
export default SvgMock;
export { SvgMock as ReactComponent };
このモックは、SVG を React コンポーネントとして扱えるようにします。
jest.config.js での設定
javascriptmodule.exports = {
moduleNameMapper: {
// SVG を React コンポーネントとして扱う場合
'\\.svg$': '<rootDir>/__mocks__/svgMock.js',
// その他の画像は通常のモック
'\\.(png|jpg|jpeg|gif|webp)$':
'<rootDir>/__mocks__/fileMock.js',
},
};
テストでの使用例
typescript// Logo.test.tsx
import { render } from '@testing-library/react';
import { ReactComponent as Logo } from './logo.svg';
test('SVG コンポーネントがレンダリングされる', () => {
const { container } = render(<Logo />);
// モックされた SVG 要素が存在することを確認
const svg = container.querySelector('svg');
expect(svg).toBeInTheDocument();
});
まとめ
Jest の moduleNameMapper は、モダンなフロントエンド開発に欠かせない設定です。パスエイリアス、CSS Modules、静的アセットなど、様々なインポート形式に対応することで、快適なテスト環境が実現できます。
本記事でご紹介した設定パターンは、以下のような場面で役立ちます。
重要なポイント:
- パスエイリアス: TypeScript や Webpack の設定と一致させることで、ソースコードとテストで同じインポート記法が使える
- CSS Modules:
identity-obj-proxyを使うことで、クラス名の検証が可能になる - 静的アセット: モックファイルで置き換えることで、バイナリファイルの解析エラーを回避できる
- 設定の順序: より具体的なパターンを先に記述することで、正しくマッチングされる
- ESM 対応:
transformIgnorePatternsと組み合わせて、ESM パッケージにも対応可能
適切な moduleNameMapper の設定により、「テストが通らない」というストレスから解放され、本来のテストロジックに集中できるようになるでしょう。
早見表を手元に置いておけば、新しいプロジェクトでもすぐに設定を完了できます。ぜひ、この記事をブックマークして、実務でご活用ください。
関連リンク
articleJest moduleNameMapper 早見表:パスエイリアス/静的アセット/CSS を一網打尽
articleJest の ESM/NodeNext 設定完全ガイド:transformIgnorePatterns と resolver 設計
articleJest の DOM 環境比較:jsdom vs happy-dom — 互換性・速度・安定性
articleJest の “Cannot use import statement outside a module” を根治する手順
articleJest の並列実行はなぜ速い?実行キューとワーカーの舞台裏を読み解く
articleJest を可観測化する:JUnit/SARIF/OpenTelemetry で CI ダッシュボードを構築
articleLangChain 再ランキング手法の実測:Cohere/OpenAI ReRank/Cross-Encoder の効果
articleJotai 非同期で Suspense が発火しない問題の切り分けガイド
articleJest moduleNameMapper 早見表:パスエイリアス/静的アセット/CSS を一網打尽
articleComfyUI ワークフロー設計 101:入力 → 前処理 → 生成 → 後処理 → 出力の責務分離
articleGitHub Copilot でリファクタ促進プロンプト集:命名・抽象化・分割・削除の誘導文
articleCodex で既存コードを読み解く:要約・設計意図抽出・依存関係マップ化
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来