Claude Media
Claude CodeでJest 30へアップグレード — 破壊的変更を順に潰す手順

Claude CodeでJest 30へアップグレード — 破壊的変更を順に潰す手順

Jest 29から30への移行をClaude Codeに任せる手順です。公式ガイドの破壊的変更を機械置換と要判断に分け、CLAUDE.mdとpermissionsの設定例、スナップショット更新の注意まで扱います。

Jest 29から30への移行は、Claude Codeに「公式の移行ガイドの項目を上から潰して、テストを回して確認する」という形で渡すと進めやすい作業です。Jest 30の移行ガイドは、互換性・マッチャー・設定・実行時の挙動・スナップショット・モックAPI・内部構造の7区分に分かれています。項目ごとに「検出できるか」「機械的に直せるか」が違うため、先に仕分けておくとClaudeの手戻りが減ります。

この記事では、着手前の環境確認、CLAUDE.mdとpermissionsの準備、項目別の検出と修正、スナップショット更新の扱いを順に説明します。

先に仕分ける:機械置換で済む項目と判断が要る項目

移行ガイドの項目は、作業の性質で3種類に分けられます。この仕分けが、Claudeに渡す指示の粒度を決めます。

種類該当する項目進め方
機械置換該当する項目エイリアスマッチャー、--testPathPattern、genMockFromModule、SpyInstance進め方grepで列挙し、一括で置換する
テストを回して判断該当する項目スナップショット、非列挙プロパティ、未処理のPromise拒否、jest.mockのパス大文字小文字進め方失敗したテストを1件ずつ読んで直す
環境・周辺ツール該当する項目Node、TypeScript、jsdom、カスタムシーケンサー、内部モジュールへの深いimport進め方先に前提を確認し、該当時だけ対応する

機械置換は、Claudeに任せるよりESLintの自動修正やgrepで済ませたほうが速く確実です。Claudeの出番は「判断が要る項目」と「置換後のテスト失敗の切り分け」になります。

着手前に確認する前提

Jest 30はNode 14・16・19・21のサポートを終了し、最小のNodeは18.xです。TypeScriptの最小バージョンは5.4で、jest-environment-jsdomはJSDOM 26を使います。Claudeに触らせる前に、手元の環境を確認します。

node --version
npx tsc --version
npm ls jest jest-environment-jsdom
npx jest --version

Nodeが17以下や19、21ならJest 30は動きません。ここはClaudeに直させず、.nvmrcやCIのNode指定を人が先に更新します。JestのTypeScript対応は5.4以上が対象なので、それより古い場合はこちらも別コミットで先に上げます。

Claude Codeに渡す準備:CLAUDE.mdとpermissions

移行中はテストを何度も回すので、実行コマンドと禁止事項をCLAUDE.mdに書いておきます。CLAUDE.mdはコンテキストとして読まれるもので、強制力はありません。確認の省略や禁止は設定ファイルの permissions で決めます。

CLAUDE.mdには、移行作業用の短い節を足す程度で足ります。1ファイル200行以下を目安にするという案内があるため、移行が終わったら節ごと消します。

## Jest 30 移行(作業中のみ)
- テストは `npx jest --ci` で実行する。watchモードは使わない
- 対象を絞るときは `npx jest --testPathPatterns "<パターン>"` を使う
- `--testPathPattern`(単数形)、`jest --init`、`toBeCalled` などの旧エイリアスは使わない
- スナップショットは、差分を読んで理由を説明するまで `-u` で更新しない
- 失敗したテストを通すために、アサーションを弱めたり削除したりしない

permissionsは、テスト実行を許可し、スナップショット一括更新を確認に回す構成が扱いやすくなります。ルールはdeny、ask、allowの順に評価され、最初に一致したものが結果を決めます。

{
  "permissions": {
    "allow": [
      "Bash(npx jest *)",
      "Bash(npx eslint *)",
      "Bash(git diff *)"
    ],
    "ask": [
      "Bash(npx jest -u*)",
      "Bash(npx jest * -u*)",
      "Bash(npx jest * --updateSnapshot*)"
    ]
  }
}

askがallowより先に評価されるため、-u を含む呼び出しだけが確認に回ります。npx は取り除かれないラッパーなので、ルールは npx jest から書きます。

非対話でまとめて回す場合は、claude -p に --allowedTools を付けます。公式の例は次の形です。

claude -p "Run the test suite and fix any failures" \
  --allowedTools "Bash,Read,Edit"

移行では、この形よりも対話セッションで項目ごとに区切って進めるほうが、差分を読みながら判断できます。テストを書かせる一般的な進め方はClaude Codeでテストを書かせる実践手順にまとめています。

手順1:Jest 29のままベースラインを取る

アップグレードの前に、現状のテスト結果を記録します。これがないと、失敗が移行由来か元からかを区別できません。

npx jest --ci 2>&1 | tail -n 15
git switch -c chore/jest-30

Claudeには、通ったテスト数と失敗したテスト名を記録させます。元から失敗しているテストが見つかったら、移行とは別の問題として切り離します。

手順2:Jest 30へ上げて、失敗を分類させる

パッケージを上げます。jest-environment-jsdom を使っているなら、同じタイミングで30系にします。

npm install --save-dev jest@^30 jest-environment-jsdom@^30
npx jest --ci 2>&1 | tee jest30-first-run.log | tail -n 30

最初の実行結果は、ログファイルに残しておきます。Claudeへの依頼は、修正ではなく分類から始めます。例えば次のような指示です。

jest30-first-run.log の失敗を、Jest 30の移行ガイドの区分
(マッチャー / 設定 / 実行時の挙動 / スナップショット / モックAPI / 内部構造)
に分類して表にしてください。まだファイルは変更しないでください。

分類させると、「失敗の大半が同じ原因」なのか「複数の区分にまたがっている」のかが見えます。同じ原因が大半なら、その項目から潰すのが最短です。

手順3:マッチャーのエイリアス削除を機械置換する

Jest 26で非推奨になっていたエイリアスが、Jest 30で完全に削除されました。機能は同じで名前だけが違います。置換表は次のとおりです。

削除された名前置換先
toBeCalled()置換先toHaveBeenCalled()
toBeCalledTimes(n)置換先toHaveBeenCalledTimes(n)
toBeCalledWith(arg)置換先toHaveBeenCalledWith(arg)
lastCalledWith(arg)置換先toHaveBeenLastCalledWith(arg)
nthCalledWith(n, arg)置換先toHaveBeenNthCalledWith(n, arg)
toReturn()置換先toHaveReturned()
toReturnTimes(n)置換先toHaveReturnedTimes(n)
toReturnWith(val)置換先toHaveReturnedWith(val)
lastReturnedWith(val)置換先toHaveLastReturnedWith(val)
nthReturnedWith(n, val)置換先toHaveNthReturnedWith(n, val)
toThrowError(message)置換先toThrow(message)

eslint-plugin-jest を入れているプロジェクトなら、no-alias-methods ルールの自動修正で一括置換できます。入っていない場合に限り、Claudeに置換を任せます。その前に使用箇所を数えます。

grep -rEn "\.(toBeCalled|toBeCalledTimes|toBeCalledWith|lastCalledWith|nthCalledWith|toReturn|toReturnTimes|toReturnWith|lastReturnedWith|nthReturnedWith|toThrowError)\(" \
  --include="*.test.*" --include="*.spec.*" src | wc -l

この正規表現は toReturn( のように括弧まで含めて照合するため、toHaveReturned( には一致しません。件数が出たら、Claudeに「この一覧を置換表のとおりに書き換え、変更後に npx jest --ci を実行して件数が増えていないことを示す」よう依頼します。置換だけの変更は、レビューしやすいよう独立したコミットにします。

手順4:CLI・設定まわりを直す

CLIと設定の変更は、package.jsonのscriptsやCI設定に隠れていることが多い項目です。Claudeには、リポジトリ全体を検索させます。

grep -rn -E "testPathPattern|jest --init|--maxWorkers|--selectProjects|--filter" \
  package.json .github jest.config.* 2>/dev/null

見つけたら、次の変更を当てます。

  • --testPathPattern は --testPathPatterns に変わりました。複数のパターンは、スペース区切りか、フラグの繰り返しで渡します。
  • jest --init は削除されました。設定ファイルの雛形は npm init jest@latest で作ります。
  • --maxWorkers や --selectProjects のように引数が要るフラグは、値がないとエラーになります。--maxWorkers=50% の形で値を付けます。
  • --filter で渡すフィルタ実装は、{filtered: Array<string>} の形のオブジェクトを返す必要があります。

.mts と .cts は、moduleFileExtensions の既定に含まれるようになりました。testMatch と testRegex の既定も .mjs・.cjs・.mts・.cts を拾います。テストではないこれらの拡張子のファイルが、テストとして実行される可能性があります。想定外のテストが走り出したら、testMatch を明示して範囲を絞ります。逆に、これらの拡張子を拾うために入れていた独自設定は、削除できる場合があります。

glob周りも変わりました。依存するglobがv10に上がり、testMatch や moduleNameMapper のパターンが以前と同じようには一致しない場合があります。パターンが効かなくなったときは、設定側を新しいglobの挙動に合わせて直します。

手順5:モックAPIの変更を直す

モックまわりでは、3点が壊れます。

モック

モックAPIで直す3点

  • genMockFromModule

    jest.genMockFromModule('fs') は削除されました。jest.createMockFromModule('fs') に置き換えます。

  • SpyInstance 型

    @jest/globals から型を明示的にimportしている場合、MockFunctionMetadata・MockFunctionMetadataType・SpyInstance が公開APIから消えています。jest.SpyInstance は jest.Spied に変えます。

  • jest.mock のパス

    jest.mock('./path/to/FILENAME.js') は、実ファイルが filename.js でも動いていました。Jest 30では大文字小文字が厳密に一致しないとモックされません。

大文字小文字の問題は、macOSやWindowsのように大文字小文字を区別しないファイルシステムで開発していると、テストが通ってしまう点が厄介です。CIがLinuxなら、そこで初めて落ちます。ローカルで失敗しないのにCIで落ちるときは、このパスの綴りを疑います。

genMockFromModule と SpyInstance は、grepで件数を確認してからClaudeに置換させます。

手順6:スナップショットは差分を読んでから更新する

スナップショットは、Jest 30で出力が変わる箇所が複数あります。

  • 以前の出力に含まれていた短縮URL(goo.gl)が、短縮されていない完全なURLに置き換わります。
  • エラーに cause があれば、スナップショットに含まれます。
  • Reactのシリアライザは、空文字列の子要素("")を出力しなくなります。
  • ArrayBuffer と DataView は、内部フィールドを持つオブジェクトではなく、読みやすい形で出力されます。

Jest 30の公開記事は、スナップショットの更新が必要になる旨を挙げています。一括で jest -u を実行すれば通りますが、そうすると「移行による想定内の差」と「本物の回帰」が区別されないまま、期待値が上書きされます。

そこで、次の順に進めます。

手順

スナップショット更新の進め方

  1. 1

    失敗したスナップショットを分類する

    Claudeに、差分を上の4種類(URL・cause・空文字列・バッファ表示)のどれかに分類させます。どれにも当てはまらない差分は、更新せず別に抜き出します。

  2. 2

    分類できた差分だけを更新する

    対象ファイルを指定して更新します。npx jest path/to/file.test.ts -u の形です。先に示した設定では、-u を含む呼び出しは確認に回るので、更新の前に人が内容を確認できます。

  3. 3

    更新後の差分を読む

    git diff で、スナップショットの変更行が分類どおりかを確認します。想定外の行があれば、そのテスト対象のコードを疑います。

テストを通すためだけの修正を防ぐ書き方は、Claudeがテストだけ通すハードコードを防ぐプロンプトの書き方で扱っています。

手順7:テストを回して判断が要る項目を直す

ここからは、失敗したテストを1件ずつ読む作業です。次の項目が該当します。

非列挙プロパティ。非列挙(non-enumerable)のオブジェクトプロパティが、オブジェクト用のマッチャーから既定で除外されます。expect.objectContaining や等価性の検査が影響を受ける可能性があります。非列挙のプロパティを検査しているテストが候補です。

未処理のPromise拒否。Jest 30は、拒否されたPromiseが後で処理される場合でも、誤って失敗させないよう修正されました。拒否が本当に未処理か確認するため、イベントループを1周余分に待ちます。そのため、意図的にPromiseを拒否させるテストでは、完了が少し遅くなる可能性があります。以前の挙動に戻す設定として waitForUnhandledRejections が追加されましたが、既定のままで足りるケースが大半です。

TypeScriptの型エラー。 toHaveBeenCalledWith 系の型が、関数の引数の型を推論するようになりました。実行時の挙動は変わりませんが、関数が数値を受け取るのに expect(fn).toHaveBeenCalledWith("string") と書いていたようなテストは、型エラーになります。引数をテスト対象の型に合わせて直します。意図的に違う型で呼ぶ場合だけ、型キャストを使います。

jsdom。 jest-environment-jsdom がJSDOM 26になった影響で、DOMの挙動や警告が変わる場合があります。Jestの公開記事は、既知の問題として、テストで window.location をモックしているケースを挙げています。jsdomを使うテストが大量に落ちたときは、まず window.location の差し替え箇所を確認します。

内部モジュールへのimport。 require('jest-runner/build/testWorker') のような、公開APIではない深いimportは動かなくなります。Jestのパッケージが単一ファイルにまとめられたためです。カスタムのテストツールを作っている場合だけの話で、通常のCLIと設定の利用には影響しません。

カスタムのテストシーケンサー(TestSequencer を継承するクラス)を使っている場合は、globalConfig と contexts が渡されるようになった点に合わせて修正します。jest.runCLI などプログラムからJestを呼ぶ場合は、Runtime の生成に globalConfig が必須になりました。

Claudeに失敗を渡すときは、1回の依頼を1つの失敗原因に絞ります。複数の原因を一度に渡すと、修正が混ざって、どの変更が効いたかが追えなくなります。

手順8:コミットを分けて、最後に全体を回す

移行は、次の単位でコミットを分けると、問題が出たときに戻しやすくなります。

  1. Node・TypeScriptなど前提の更新
  2. package.json の依存更新
  3. マッチャーのエイリアス置換
  4. CLI・設定の変更
  5. モックAPIの変更
  6. スナップショットの更新
  7. 型エラーや挙動差の個別修正

最後に、ベースラインと同じ条件でテストを全件実行します。

npx jest --ci 2>&1 | tail -n 15

手順1で記録した数と比べ、通ったテスト数が減っていないことを確認します。テストの削除やスキップが紛れていないかは、git diff --stat と git diff | grep -E "^\+.*(\.skip|xit|xdescribe)" で確かめます。

テストを継続して自動実行させたい場合は、Claude Code hooksでテストを自動実行するの構成が使えます。テストが少ないコードを触る場合は、Claude Codeリファクタリングを安全に進める手順の進め方が参考になります。

移行後に試せる設定

移行が済んだら、Jest 30で追加された設定を確認します。ここは必須ではありません。

  • globalsCleanup:テストファイルごとにグローバルが適切に片付けられていないと、警告が出ます。既定は soft です。警告が出ないプロジェクトでは 'on' にして、メモリ使用量の削減を狙えます。
  • expect.arrayOf:配列の全要素が条件に合うかを検査する、新しい非対称マッチャーです。
  • TypeScriptによる設定ファイル:jest.config.ts を書けます。

Jestの公開記事には、大規模なTypeScriptアプリで一部のテストが37%速くなり、メモリ使用量が77%減った例が載っています。これは特定のプロジェクトの計測値で、移行すれば同じ効果が出るとは限りません。

この記事を共有:XはてブLinkedIn