[React] JestプロジェクトにVitestを導入する - Viewテスト分離戦略

2026-03-09 hit count image

既存のJestで作成されたReactプロジェクトにVitestを追加し、ViewテストとロジックテストをランナーごとにOK分離する方法を紹介します。モノレポ環境での設定方法とCI統合まで解説します。

react

概要

既存のJestで全テストを作成していたReactプロジェクトにVitestを導入した経験を共有します。全テストを一度にマイグレーションするのではなく、View(コンポーネント)テストはVitestで、Actions/ロジックテストはJestで分離する戦略を取りました。

なぜVitestを追加するのか

JestはReactエコシステムで長らく標準テストフレームワークとして使われてきましたが、いくつかの限界があります。

  • Viteとの二重設定: プロダクトコードはViteでビルドしながらテストはJestで実行すると、ts-jestjest-svg-transformerなど別途の変換設定が必要です。
  • import.meta.envのサポート: Viteプロジェクトで環境変数をimport.meta.envでアクセスしますが、Jestではts-jest-mock-import-metaのような別途パッケージが必要です。
  • スナップショットテストのメンテナンスコスト: Jestスナップショットテストは、HTML構造の変更だけで壊れやすく、大規模なスナップショットファイルはレビューが困難です。

VitestはViteネイティブのテストフレームワークで、これらの問題を根本的に解決します。

  • Viteのモジュール解決と変換パイプラインをそのまま使用するため、別途の変換設定が不要です。
  • import.meta.envをネイティブでサポートします。
  • happy-domを使用すると、jsdomより高速なDOM環境を提供します。

戦略:テストランナーの分離

一度に全テストをマイグレーションするとリスクが大きいため、テスト対象に応じてランナーを分離しました。

テスト対象ランナー理由
View(コンポーネント)テスト(view/配下)VitestVite環境と一致、CSS/SVG処理が自然
Actions/Controller/Utils/SelectorsテストJest既存テスト資産の活用、段階的マイグレーション

この分離により、以下のメリットを得られました。

  • 既存のロジックテストに触れずに安全にVitestを導入できる
  • ViewテストからJestの複雑な変換設定(ts-jest-mock-import-metadotenvなど)を削除できる
  • 将来的に全テストをVitestに統合する基盤を構築

Vitest環境設定

それでは、Vitestをプロジェクトに追加する方法をステップごとに見ていきましょう。

パッケージインストール

次のコマンドを実行して、Vitestと関連パッケージをインストールします。

yarn add -D vitest @vitest/ui happy-dom

各パッケージの役割は以下の通りです。

パッケージ役割
vitestテストランナー(Jest互換API提供)
@vitest/uiテスト結果をブラウザUIで確認
happy-dom軽量DOM環境(jsdom代替、より高速な実行速度)

happy-domjsdomよりはるかに高速なDOM実装です。Viewコンポーネントのレンダリングテストに適しており、ほとんどのDOM APIをサポートしています。

package.jsonスクリプト追加

Vitest実行用のスクリプトを以下のようにpackage.jsonファイルに追加します。

{
  "scripts": {
    "test": "TZ=Asia/Tokyo jest --watchAll",
    "test:ci": "TZ=Asia/Tokyo jest --ci --bail",
    "test:view": "TZ=Asia/Tokyo vitest",
    "test:view:ci": "TZ=Asia/Tokyo vitest run"
  }
}
  • test:view: ローカル開発時にwatchモードでViewテストを実行
  • test:view:ci: CI環境で一度だけ実行して終了

既存のtesttest:ciスクリプトはJestをそのまま使用します。TZ=Asia/Tokyoは日付/時刻関連テストの一貫性のためにタイムゾーンを固定しました。

Jest設定変更

JestがViewテストを実行しないように設定を変更する必要があります。jest.config.jsファイルを開き、以下のように修正します。

// jest.config.js
export default {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
  testMatch: [
    '**/controller/**/*.test.ts',
    '**/actions/**/*.test.ts',
    '**/utils/**/*.test.ts',
    '**/selectors/**/*.test.ts',
  ],
  testPathIgnorePatterns: ['/node_modules/', '/view/'],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
    '^.+\\.svg$': 'jest-svg-transformer',
    '\\.(css|scss)$': 'identity-obj-proxy',
  },
  setupFilesAfterEnv: ['<rootDir>/jest-setup.ts'],
  clearMocks: true,
};

主な変更点は以下の通りです。

  • dotenvのimport削除: VitestではViteが環境変数を自動的に処理するため、JestでもActionsテストには不要になりました。
  • testMatch追加: controller/actions/utils/selectors/ディレクトリのテストのみ実行するよう制限しました。
  • testPathIgnorePatterns/view/追加: view/配下のファイルを明示的に除外します。
  • transform設定削除: ts-jest-mock-import-metaが不要になり、transform設定全体をシンプルにしました。

このようにJestのテスト範囲を狭めることで、ViewテストがJestで実行されず、2つのランナー間の衝突を防止できます。

Vite + React + TypeScriptプロジェクトでJestを設定してテストコードを作成する方法について詳しく知りたい方は、[Vite] TypeScriptベースのReactプロジェクトにテスト環境を構築するを参考にしてください。

既存Viewテストの整理

Vitest導入とともに、既存のJestベースのViewテストを整理しました。

スナップショットテスト削除

既存のViewテストは、以下のようにほとんどがJestスナップショットテストで作成されていました。

// 変更前:Jestスナップショットテスト
import { render } from '@testing-library/react';
import { ItemPanel } from '.';

test('render well', () => {
  const { container } = render(
    <ItemPanel
      label="label"
      value="value"
      showButton
      buttonLabel="変更"
      helpText="help"
    />
  );
  expect(container).toMatchSnapshot();
});

スナップショットテストには以下のような問題がありました。

  • メンテナンスコストが高い: HTML構造が少し変わるだけでスナップショットが壊れ、61行以上のスナップショットdiffをレビューする必要があります。
  • 意味のある検証ではない: 「HTMLが以前と同じ」ということだけを確認するだけで、実際にユーザーに正しく表示されるか検証できません。
  • 手動Mockの複雑さ: コンポーネント間の依存関係を断つために__mocks__/ディレクトリに複雑なMockファイルを作成する必要がありました。

これらのスナップショットファイル(__snapshots__/)と__mocks__/ディレクトリを削除し、Vitestで新たに作成できる基盤を整えました。

// 変更後:Vitestで新たに作成できる基盤
describe('ItemPanel', () => {
  test('test', () => {
    // テストを書く
  });
});

このPRでは+1,139行追加、-10,134行削除で、大量のスナップショットとMockコードを整理しました。

CI連携 (GitHub Actions)

既存のCIワークフローにVitest実行ステップを追加しました。

# .github/workflows/check_code_service_a.yml(変更前)
- name: Test
  run: yarn test:ci:serviceA -- --shard=${{ matrix.shard }}/10
# .github/workflows/check_code_service_a.yml(変更後)
- name: Test actions
  run: yarn test:ci:serviceA -- --shard=${{ matrix.shard }}/10
- name: Test view
  run: yarn test:view:ci:serviceA -- --shard=${{ matrix.shard }}/10

変更点はシンプルです。

  • 既存のTestステップをTest actionsに名前変更(Jest実行)
  • Test viewステップ追加(Vitest実行)
  • 両ステップとも--shardオプションを使用して並列でテストを分散実行

VitestもJestと同様に--shardオプションをサポートしているため、既存CIの並列実行戦略をそのまま活用できます。

まとめ

この記事では、既存のJestプロジェクトにVitestを段階的に導入する方法を紹介しました。

項目内容
導入戦略ViewテストはVitest、ロジックテストはJestで分離
Vitest環境vitest + happy-dom + @vitest/ui
Jest設定変更testMatchtestPathIgnorePatternsでViewテスト除外
既存テスト整理スナップショットテストと手動Mock削除(-10,134行)
不要な依存関係の削除dotenvts-jest-mock-import-meta削除
CI連携GitHub ActionsにTest viewステップ追加、shard並列実行対応

新しいツールを導入する際の核心は 「一度に全てを変えない」 ということです。テスト対象に応じてランナーを分離すれば、既存のテスト資産を維持しながら新しいツールのメリットを享受できます。将来的にロジックテストもVitestにマイグレーションすれば、Jestへの依存を完全に排除できます。

モノレポ環境でのテスト方針策定プロセスとツール選定についてはこちらのブログ[React] モノレポ環境での共通コンポーネントテスト方針を、Vitestを活用したVRT(Visual Regression Testing)構築についてはこちらのブログ[React] VitestでコンポーネントVRT+アクセシビリティテスト環境構築も参考にしてみてください。

私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!

アプリ広報

今見てるブログを作成たDekuが開発したアプリを使ってみてください。
Dekuが開発したアプリはFlutterで開発されています。

興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。



SHARE
Twitter Facebook RSS