結論から言うと、2026年現在、JavaScript / TypeScript のテストランナーを新しく選ぶなら第一候補は Vitest です。

そして今 Jest を使っているプロジェクトでも、Vitest は Jest 互換の API を持っているため、移行は想像よりずっと簡単です。中規模プロジェクトなら半日〜1日で移行できます。

この記事では、Jest + React Testing Library で書かれた既存のテストを Vitest に移行する手順を、パッケージの入れ替え → 設定ファイル → コードの書き換え → ハマりどころの順に、コピペで進められる形でまとめます。

独学に限界を感じたら

プログラミング教室がおすすめ。挫折せず最短で実力を伸ばせます。

PR

なぜ今Vitestなのか(2026年の現在地)

Vitest は 2025年10月の v4.0 で Browser Mode(jsdom ではなく本物の Chromium でコンポーネントをテストする機能)が安定版になり、2026年3月の v4.1 では Vite 8 に対応しました。

v4.0 リリース時点のアナウンスによると、週間ダウンロード数はこの1年で約700万から約1700万へ伸びており、勢いは数字にも表れています。

  • Vite と同じ変換パイプラインで動くので、TypeScript・ESM・パスエイリアスが追加設定なしでそのまま通る(ts-jest や babel-jest が不要になる)
  • watch モードが高速。変更したファイルに関係するテストだけを賢く再実行してくれる
  • describe / it / expect / vi.fn / vi.mock など、Jest とほぼ同じ書き味の API
  • jsdom での軽量なテストと、本物のブラウザで動かす Browser Mode を用途で選べる

Jest も 2025年に v30 がリリースされておりメンテナンスは続いていますが、ESM 対応は長らく実験的な位置づけのままです。ビルドに Vite を使っているプロジェクトなら、テストだけ Babel / ts-jest の変換設定を別に持つ二重管理をやめて Vitest に揃えるのが素直な選択です。

移行手順1: パッケージ入れ替えと設定ファイル

まず Jest 関連のパッケージを外し、Vitest を入れます。DOM を使うテスト(React コンポーネントのテストなど)をするなら jsdom も一緒に入れます。

# Jest 関連を削除(入っているものだけでOK)
npm uninstall jest ts-jest babel-jest @types/jest jest-environment-jsdom

# Vitest を追加(DOM を使うなら jsdom も)
npm install -D vitest jsdom

次に設定ファイルです。すでに Vite を使っているなら vite.config.ts に test プロパティを足すだけでも動きますが、テスト設定を分けたい場合は vitest.config.ts を作ります。

// vitest.config.ts
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,          // describe / it / expect / vi を import なしで使う
    environment: 'jsdom',   // DOM を使うテスト用
    setupFiles: ['./vitest.setup.ts'],
  },
})

Jest ではグローバル API(describe や expect)がデフォルトで有効ですが、Vitest ではデフォルト無効です。globals: true を指定すると Jest と同じ感覚で書けるので、既存テストを書き換えずに済みます。

TypeScript の型を通すために tsconfig.json にも型定義を追加しておきます。

{
  "compilerOptions": {
    "types": ["vitest/globals", "@testing-library/jest-dom"]
  }
}

package.json のスクリプトも置き換えます。vitest はデフォルトが watch モードなので、CI で使う一発実行は vitest run です。

{
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest"
  }
}

移行手順2: jest.* を vi.* に書き換える

コード側の書き換えは、大半が jest オブジェクトを vi に置き換えるだけの機械的な作業です。globals: true にしていれば vi もグローバルに生えているので、import の追加も不要です。

  • jest.fn() → vi.fn()
  • jest.spyOn() → vi.spyOn()
  • jest.mock('./api') → vi.mock('./api')
  • jest.requireActual('./api') → await vi.importActual('./api')
  • jest.useFakeTimers() → vi.useFakeTimers()
  • jest.clearAllMocks() → vi.clearAllMocks()

注意が必要なのは jest.requireActual です。Vitest では非同期の vi.importActual に変わるため、単純置換では済みません。ここだけは手で直しましょう。それ以外は一括置換でほぼ通ります。

モック周りの3つの違い(ここでハマる)

API 名の置換だけでは済まない、挙動そのものの違いが3つあります。公式の移行ガイドでも明記されているポイントです。

1. vi.mock の factory は「全 export」を返す

Jest では factory の戻り値がそのまま default export として扱われますが、Vitest では「モジュールの各 export を明示したオブジェクト」を返す必要があります。default export をモックするなら default キーを明示します。

// Jest: 戻り値がそのまま default export になる
jest.mock('./some-path', () => 'hello')

// Vitest: 全 export を明示したオブジェクトを返す
vi.mock('./some-path', () => ({
  default: 'hello',
}))

2. factory の外の変数は vi.hoisted で包む

vi.mock はファイルの先頭に巻き上げ(ホイスティング)されて実行されます。そのため factory の中から通常のトップレベル変数を参照すると「初期化前に参照した」というエラーになります。モック関数を外から操作したいときは vi.hoisted を使います。

// NG: vi.mock は先頭に巻き上げられるので、この時点で mockFn は未定義
const mockFn = vi.fn()
vi.mock('./api', () => ({ fetchUser: mockFn }))

// OK: vi.hoisted で定義すれば factory から参照できる
const { mockFetchUser } = vi.hoisted(() => ({
  mockFetchUser: vi.fn(),
}))

vi.mock('./api', () => ({
  fetchUser: mockFetchUser,
}))

test('モックの戻り値を差し替えられる', async () => {
  mockFetchUser.mockResolvedValue({ name: 'taro' })
  // ...
})

3. mockReset の意味が違う・__mocks__ は自動で読まれない

Jest の mockReset はモックを「空の関数」に置き換えますが、Vitest の mockReset は「vi.fn(impl) に渡した元の実装」に戻します。リセット後の挙動を当てにしたテストは動きが変わるので注意してください。

また、Jest がデフォルトで読み込む __mocks__ ディレクトリの自動モックは、Vitest では vi.mock('モジュール名') を呼んだときにだけ適用されます。

React Testing Library と組み合わせる

React Testing Library はそのまま使えます。jest-dom のカスタムマッチャー(toBeInTheDocument など)は、Vitest 用のエントリポイントを setup ファイルで読み込むだけです。

// vitest.setup.ts
import '@testing-library/jest-dom/vitest'

テストコード自体は Jest 時代と1文字も変わりません。次のようなテストがそのまま通ります。

import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Counter } from './Counter'

test('クリックでカウントが増える', async () => {
  render(<Counter />)
  await userEvent.click(screen.getByRole('button', { name: '増やす' }))
  expect(screen.getByText('count: 1')).toBeInTheDocument()
})

そのほかの細かいハマりどころ

  • ネストした describe のテスト名の区切りが、Jest の空白ではなく「>」で結合される(テスト名でフィルタしている CI 設定は要確認)
  • Jest のレガシータイマー(legacy fake timers)は非対応。モダンタイマー前提のコードならそのまま動く
  • 環境変数のモックは vi.stubEnv('API_URL', 'http://test') が使える(vi.unstubAllEnvs で戻す)
  • ワーカー識別は JEST_WORKER_ID の代わりに VITEST_POOL_ID / VITEST_WORKER_ID を参照する
  • Vue コンポーネントのスナップショットを移行する場合は jest-serializer-vue の指定が必要

まとめ: 置換8割・要注意2割

移行作業の実感としては、jest → vi の単純置換で8割が終わり、残り2割が vi.mock の factory・vi.hoisted・mockReset まわりの挙動差の吸収です。逆に言えば、ハマりどころはこの記事に書いた範囲にほぼ集約されるので、先に知ってから着手すれば安全に移行できます。

  • パッケージを入れ替え、vitest.config.ts に globals: true / environment: 'jsdom' / setupFiles を設定する
  • jest.* を vi.* に置換する(requireActual だけは await vi.importActual に手で直す)
  • vi.mock の factory は全 export を返す。外の変数は vi.hoisted で包む
  • jest-dom は @testing-library/jest-dom/vitest を setup で読み込むだけ

これから Jest を新規にセットアップする場合や、Jest のまま運用する場合の設定は、以下の記事で解説しています。

Reactテスト入門|Vitestで始める2026年の新標準【Jest比較】CRA前提のJest入門はもう古い——2026年にReactのテストを始めるなら「Vite + Vitest + React Testing Library」がほぼ設定ゼロの新標準です。レンダリング・クリック・非同期の動くテストコード3例と、Jestからの移行が簡単な理由まで現役エンジニアが解説します。frontendlab.magicgifted.com Next.js 16×Jest導入ガイド|App Router対応の最新設定【2026年】Next.js 16のApp RouterでJestを動かす最新手順を解説。next/jestを使えばts-jestは不要で、TypeScriptもそのまま動きます。動くテスト例3つ、Server Componentテストの注意点、Vitestとの比較まで2026年基準でまとめました。frontendlab.magicgifted.com