Jestは、Node.js上で高速にテストを実行できるJavaScript/TypeScript向けのテストランナーです。Angularで長く使われてきたKarma/Jasmineは、ブラウザを起動してテストする前提が強く、実行速度やCI安定性、設定の追いやすさでつらさが出ることがあります。Jestへ移行すると、watch実行やキャッシュが使いやすくなり、テストの待ち時間を減らしやすくなります。一方で、ESM/CJSの違い、transform設定、path alias、CIキャッシュなど、移行時に詰まりやすい論点もあります。大切なのは、設定を一気に完璧にすることではなく、最小構成でまず1本動かし、詰まりどころを順番に潰すことです。
関連: Angularテストが壊れる理由TOP10(flaky/モック地獄/遅いCI)

1. なぜJestに移行するのか(判断基準)
1-1. 速度/開発体験の違い
Jestに移行する大きな理由は、テスト実行の速さと開発体験を改善しやすいことです。特に、修正してすぐ確認するサイクルを何度も回す開発では、この差がそのままストレス差になります。
Karmaはブラウザ実行を前提にしているため、ブラウザ起動や接続、実行環境の都合で重くなりやすいです。JestはNode.js上で回るので、watchモード、差分実行、キャッシュの恩恵を受けやすく、手元の反復作業が軽くなります。失敗時のログも比較的読みやすく、ローカルとCIの挙動を揃えやすいのも利点です。
実務でよく起きるのは、「テストが重いから回す回数が減る」「落ちても後回しになる」という流れです。Jestへ移行すると、この心理的コストを下げやすくなります。もちろん、設計が悪いテストまで自動で良くなるわけではありませんが、少なくとも“ブラウザ起動込みの重さ”からはかなり離れやすいです。
1-2. 移行しない判断もあり得る条件
ただし、Angularなら必ずJestに移行すべき、とは言えません。今のKarma/Jasmine運用で大きな痛みがなく、CIも安定しているなら、移行しない判断も十分あり得ます。
特に、既存テストが大量にあり、独自セットアップやブラウザ依存のテストが多い場合は、移行コストが無視できません。また、チーム内にJestの知見が少なく、Angular標準寄りの構成を優先したい場合は、Karma/Jasmine維持のほうが安全なこともあります。移行は「新しいから良い」ではなく、「今の不満をどれだけ減らせるか」で判断するほうが失敗しにくいです。
判断基準としては、「テストが遅くて回らない」「CIでブラウザ起因の不安定さがある」「watch体験が悪い」「他リポジトリとJestへ揃えたい」といった課題があるかを見るのが分かりやすいです。逆に、それらの痛みが小さいなら、移行より先にテスト設計やモック境界を見直すほうが効果的な場合もあります。
2. 最小の移行手順(まず動かす)
2-1. 依存追加と設定ファイルの全体像
Jest移行の最初の目標は、完璧な設定理解ではなく、まず1本の既存テストを動かすことです。そのためには、依存追加と設定ファイルの役割をざっくり押さえるのが近道です。
AngularでJestを動かすには、Jest本体に加えて、Angular向けのプリセットやTypeScript変換まわりの設定が必要になります。考える対象は大きく、Jest本体、Angular向けpreset、セットアップファイル、実行スクリプトの4つです。最初から細かい最適化を入れず、preset、testEnvironment、setupFilesAfterEnv あたりを通すだけでもかなり前進します。
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:ci": "jest --ci --runInBand --coverage"
}
}
// jest.config.ts
import type { Config } from 'jest';
const config: Config = {
preset: 'jest-preset-angular',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/setup-jest.ts'],
testMatch: ['**/+(*.)+(spec).+(ts)'],
moduleFileExtensions: ['ts', 'html', 'js', 'json']
};
export default config;
// setup-jest.ts
import 'jest-preset-angular/setup-jest';
この設定は「まず起動させる」ための最小形です。実際にはAngularやjest-preset-angularのバージョンで細部が変わることがありますが、考え方は同じです。最初からcoverageやalias、asset処理まで全部盛りにせず、まずは最小のspecが1本通るところまでを目標にすると、後の切り分けが楽になります。
2-2. 既存テストが動くまでのチェック順
Jest移行では、問題を一度に解かず、チェック順を固定することが重要です。Jestは設定が少しズレるだけで大量に落ちたように見えるため、順番を決めて進めたほうが詰まりにくいです。
おすすめの順番は、まずJest自体が起動するか、その次にAngularの最小コンポーネントテストが1本通るか、その後にServiceテスト、最後にaliasやassets、CI対応を見る流れです。最初から全specを流すと、ESM、module解決、HTTP、テンプレート依存が同時に崩れて、原因が混ざります。逆に、最小の1本を通せば、土台が正しいかどうかがかなり明確になります。
実務では、「100本落ちる」より「最初の1本がなぜ通らないか」を先に解くほうが速いです。チェックの順序としては、「Jest起動 → setup読み込み → TestBed最小テスト → 既存コンポーネント → alias/静的ファイル → CI」の順にすると切り分けしやすいです。移行で長く苦しむチームほど、ここを飛ばして一気に全部やろうとしがちです。
3. よく詰まる:ESM/CJSとtransform
3-1. エラーの典型と原因の分け方
Jest移行で一番詰まりやすいのが、ESM/CJSの違いと、それに伴うtransformまわりのエラーです。これはAngular固有というより、最近のJavaScript/TypeScript実行環境全体で起きやすい論点です。
典型的には、「Cannot use import statement outside a module」「Unexpected token export」「Jest encountered an unexpected token」といったエラーが出ます。これらは大ざっぱに言うと、「Jestが読める形式に変換できていない」「node_modules配下の依存が適切にtransformされていない」「ESM前提の依存をそのままCJSっぽく読もうとしている」といった原因に分かれます。見た目が似ていても、どのファイルで落ちているかによって対策は変わります。
切り分けるときは、まず失敗しているファイルが「自分のsrcか」「node_modulesか」を見てください。srcならTypeScript/Angular変換設定を、node_modulesならtransformIgnorePatternsやESM対応を疑うと進めやすいです。エラー文だけで決め打ちせず、「どのファイルをJestが解釈できなかったか」を見る癖をつけると、かなり早く原因に近づけます。
3-2. transform設定の考え方
transform設定は、Jestがそのまま実行できないファイルを、実行可能な形へ変換するための入り口です。ここを理解すると、ESM/CJSの詰まり方がだいぶ読みやすくなります。
Angularでは、TypeScriptだけでなくテンプレートやAngular固有のメタデータも関わるため、素のJestだけでは不十分です。そのため、Angular向けpresetがtransformの大部分を肩代わりしてくれます。ただし、一部のESM依存や最近のライブラリは追加調整が必要で、特定パッケージだけtransform対象に含めることがあります。transformは万能スイッチではなく、「読めないものだけをJestに読ませる設定」と捉えると分かりやすいです。
// jest.config.ts
import type { Config } from 'jest';
const config: Config = {
preset: 'jest-preset-angular',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/setup-jest.ts'],
transform: {
'^.+\\.(ts|mjs|js|html)$': 'jest-preset-angular'
},
transformIgnorePatterns: [
'node_modules/(?!.*\\.mjs$)'
]
};
export default config;
この設定では、TypeScript、HTML、mjs、jsをJest側で変換できるようにしています。注意点は、「全部transformすればよい」と考えないことです。無差別に変換対象を広げると逆に遅くなりやすく、別の副作用も出ます。基本は、エラーになった対象を見て必要最小限の調整を加えるほうが安定します。
4. path alias / assets / styles の扱い
4-1. moduleNameMapperの当て方
Angularでpath aliasを使っているなら、Jest側にも同じ解決ルールを教えないと import 解決で落ちます。TypeScriptのpathsだけでは、Jestはその意図を自動では理解してくれません。
たとえば @app/... や @shared/... のようなaliasは、tsconfig上では通っても、Jestの実行時には別の解決系になるため、moduleNameMapper が必要です。ここを忘れると、移行直後に「モジュールが見つからない」エラーが大量に出やすいです。Angularの問題ではなく、TypeScriptコンパイラとJestランナーの責務が違う、と理解しておくと整理しやすいです。
// jest.config.ts
import type { Config } from 'jest';
const config: Config = {
preset: 'jest-preset-angular',
moduleNameMapper: {
'^@app/(.*)$': '<rootDir>/src/app/$1',
'^@shared/(.*)$': '<rootDir>/src/app/shared/$1',
'^@env/(.*)$': '<rootDir>/src/environments/$1'
}
};
export default config;
この設定では、アプリ内で使っているaliasをJestにも解決させています。注意点は、tsconfigのpathsとJestのmoduleNameMapperがズレると、「アプリ本体では通るのにテストだけ落ちる」状態になることです。aliasを使うなら、TypeScriptとJestの両方で同じ意味になるよう揃えて管理するのが安全です。
4-2. テスト環境で不要なものを切る
Jest移行では、テストで意味を持たないものを切る発想がかなり重要です。特にstylesやassetsは、本番には必要でも、単体テストではほぼ不要なことが多いです。
AngularコンポーネントがSCSSや画像、SVG、フォントなどを参照していると、Jest実行時にそれらを解決しようとして落ちることがあります。ただし、多くのコンポーネントテストで見たいのはロジックや表示分岐であり、本物のCSSや画像データではありません。そのため、これらをスタブ化して“空として扱う”ほうが、実務では安定します。
// jest.config.ts
import type { Config } from 'jest';
const config: Config = {
preset: 'jest-preset-angular',
moduleNameMapper: {
'\\.(css|scss|sass)$': '<rootDir>/test/style-mock.js',
'\\.(png|jpg|jpeg|svg)$': '<rootDir>/test/file-mock.js'
}
};
export default config;
// test/style-mock.js
module.exports = {};
// test/file-mock.js
module.exports = 'test-file-stub';
この設定では、テストに不要なスタイルや画像をダミーとして扱っています。注意点は、スタイルそのものの有無やビジュアル差分を検証したいテストには向かないことです。ただ、通常のAngular単体テストでは、まず“ロジックと表示条件”を安定して見ることが優先なので、不要なものを切るほうが全体の品質は上がりやすいです。
5. CIで回す(速度と安定性)
5-1. キャッシュと並列の設定
JestをCIで活かすには、ローカルだけでなくCIでも速くて安定していることが重要です。ここではキャッシュと並列数の調整が効きやすい論点になります。
Jest自体にキャッシュ機構はありますが、CIではNode依存のインストールキャッシュ、Angular関連のビルドキャッシュ、そしてワーカー数の設定も影響します。並列を増やせば速くなることもありますが、CPUやメモリが弱いランナーだと、逆に不安定になったり遅くなったりします。つまり、Jestへ移行しただけでCIが自動的に最速になるわけではなく、実行環境に合わせた調整が必要です。
{
"scripts": {
"test:ci": "jest --ci --maxWorkers=50% --coverage"
}
}
この設定では、CI用にワーカー数を半分に抑えています。注意点は、ローカル最適の設定をそのままCIへ持ち込まないことです。共有ランナーや小さめの実行環境では、--maxWorkersを明示したほうが安定しやすいです。加えて、パッケージキャッシュやJestキャッシュをCIで有効にできるなら、そこも合わせて見ると効果が出やすいです。
5-2. カバレッジ閾値と運用
Jest移行後にカバレッジを使うなら、“守れる運用”として閾値を置くことが大切です。理想の数字だけを先に置くと、現場では形骸化しやすくなります。
カバレッジは品質の参考指標として便利ですが、高ければ安全というものでもありません。それでも、未テスト領域を見つけたり、最低限の保険をかけたりするには有効です。CIで閾値を強制する場合は、今のテスト資産に合った現実的なラインから始めるほうが、チームで受け入れやすいです。
// jest.config.ts
import type { Config } from 'jest';
const config: Config = {
preset: 'jest-preset-angular',
collectCoverageFrom: [
'src/app/**/*.ts',
'!src/main.ts',
'!src/**/*.module.ts'
],
coverageThreshold: {
global: {
branches: 60,
functions: 70,
lines: 75,
statements: 75
}
}
};
export default config;
この設定はあくまで一例ですが、最初から90%以上を全体強制するより、今の現実に合った基準で始めるほうが運用しやすいです。注意点は、数字だけを追って意味の薄いテストが増えることです。coverageは目的ではなく、危ない抜けを見つけるための補助線として扱うほうが、長期的には健全です。
6. まとめ:移行チェックリスト
6-1. 手順の再掲(最短ルート)
AngularをJestへ移行するときは、最小構成で1本通す → ESM/aliasを直す → CI最適化するの順が最短です。最初から全部を完璧に揃えようとしないほうが、結果的に早く進みます。
実際の流れとしては、まずJest本体とAngular向けpresetを入れ、setupファイルを通し、StandaloneコンポーネントやServiceの最小specが通るかを見ます。その後で、ESM/CJSのtransform調整、moduleNameMapperによるalias対応、assets/stylesのスタブ化、最後にCI向けのキャッシュや並列設定を入れる、という順番が整理しやすいです。
移行が長引くチームほど、最初から全テストを一括移行しようとして詰まりがちです。最短ルートは、「1本通す」「同系統の失敗をまとめて直す」「CIで安定させる」を段階的に進めることです。移行そのものを大プロジェクト化しすぎないのが成功しやすい進め方です。
6-2. 移行後に確認する項目
Jestへ移行できたあとも、“動いた”で終わらず、運用上の確認項目を持つことが大切です。特にローカルとCIで同じように安定して動くかは、必ず見ておきたいポイントです。
確認項目としては、ローカルwatchが快適か、CIで落ち方が安定しているか、coverageの収集が期待通りか、path aliasや静的ファイルの扱いに抜けがないか、ESM系依存が増えたときの修正方針があるか、などがあります。Jestへ変えたことで設定点は増えるので、移行後は“設定の置きっぱなし”を防ぐ意識も必要です。
実務では、「移行できた」より「新しいテストがJest前提で迷わず書ける」状態のほうが重要です。そのため、package scripts、jest.config、setupファイル、CIオプションの意味をチームで共有し、レビュー時にも見られる状態にしておくと、移行後の運用品質が安定しやすくなります。
7. 参考リンク
- Jest 公式ドキュメント
https://jestjs.io/ - jest-preset-angular
https://thymikee.github.io/jest-preset-angular/ - ts-jest 公式ドキュメント
https://kulshekhar.github.io/ts-jest/ - Angular Testing ガイド
https://angular.dev/guide/testing - Jest Configuration 公式
https://jestjs.io/docs/configuration