爆速テストフレームワークVitestを徹底解説!

爆速テストフレームワークVitestを徹底解説! 新時代のフロントエンドテストを極める

はじめに:なぜ今、テストが重要なのか? そしてVitestの衝撃

ソフトウェア開発において、テストは品質保証の要です。特に、近年複雑化が著しいJavaScript/TypeScriptを用いたフロントエンド開発においては、単体テスト、結合テスト、E2Eテストなど、様々なレベルでのテストが不可欠となっています。ユーザーインターフェースはインタラクティブであり、様々な状態やユーザー操作によって予期せぬバグが発生しやすいため、自動化されたテストスイートを持つことは、安定した開発、迅速な機能追加、そしてリファクタリングを安全に行うために欠かせません。

これまで、JavaScript/TypeScriptのテストフレームワークといえば、JestやMochaがデファクトスタンダードとして広く利用されてきました。これらのフレームワークは非常に強力で多機能であり、多くのプロジェクトでその恩恵を受けています。しかし、プロジェクトの規模が大きくなるにつれて、特にテスト実行時間の増大が開発体験上の課題となることがありました。テスト実行に時間がかかると、開発サイクルが遅延し、開発者はテストを書くこと自体に抵抗を感じるようになる可能性さえあります。

そんな中、彗星のごとく現れ、JavaScriptテスト界に衝撃を与えたのが「Vitest」です。Vitestは、その名の通りViteというモダンなビルドツールと密接に連携することで、これまでの常識を覆すような「爆速」なテスト実行速度を実現しました。開発者がファイルを保存するたびに、まるで開発サーバーがホットリロードするように、テストが瞬時に再実行される体験は、一度味わうと手放せなくなるほどの快適さです。

この記事では、Vitestがなぜこれほどまでに速いのか、そのアーキテクチャの秘密から、主要な機能、導入方法、実践的な使い方、そしてJestなどの他のフレームワークと比較した場合のメリット・デメリットまで、Vitestのすべてを徹底的に解説します。Vitestはまだ比較的新しいフレームワークですが、その革新性と強力な機能セットにより、すでに多くの開発者やプロジェクトで採用が進んでいます。この記事を読み終える頃には、あなたもVitestを使って爆速テスト環境を手に入れる準備ができているはずです。

さあ、Vitestの魅力に迫り、新時代のフロントエンドテストを極めましょう!

Vitestとは何か? なぜ「爆速」なのか? そのアーキテクチャに迫る

Vitestは、「Vite Native Test Framework」と謳われているように、モダンなビルドツールであるViteと密接に連携して動作するように設計されたテストフレームワークです。JestやMochaといった既存のフレームワークが、通常、独自のテストランナー、モジュールローダー、トランスパイラー(Babelなど)の設定を必要とするのに対し、VitestはViteの開発サーバーが持つ機能を最大限に再利用します。ここに、Vitestの「爆速」の最大の秘密があります。

Viteの高速性とその仕組み

まず、Vitestの基盤となっているViteがなぜ高速なのかを理解することが重要です。従来の多くのビルドツール(Webpackなど)は、開発サーバーを起動する際に、プロジェクト全体のコードをまとめてバンドル(結合)するプロセスを経ます。プロジェクトが大きくなるほど、このバンドル処理に時間がかかり、開発サーバーの起動やコード変更時のリビルドが遅延する原因となっていました。

一方、ViteはES Modules(ESM)のネイティブなサポートを利用しています。開発モードでは、Viteはコードを一切バンドルしません。ブラウザがESMの import 文を読み込むたびに、Viteは要求されたモジュールを即座に変換(例えばTypeScriptをJavaScriptに変換)し、ブラウザに提供します。これにより、開発サーバーの起動はほぼ瞬時に完了し、コード変更時も変更されたモジュールとその依存関係のみをブラウザに提供するため、非常に高速なホットモジュールリプレースメント(HMR)を実現します。

VitestがViteの高速性をどう引き継ぐか

Vitestは、このViteの開発サーバーの仕組みをテスト実行にも利用します。具体的には:

  1. Viteの開発サーバーの再利用: Vitestは内部でViteの開発サーバーインスタンスを起動または既存のインスタンスにアタッチします。テストファイル内の import 文は、このViteサーバーによって解決され、提供されます。これにより、Viteプロジェクトで既に設定しているエイリアス、パス解決、Viteプラグイン(TypeScriptコンパイル、Vue/React SFC処理など)が、テスト実行時にもそのまま適用されます。Jestのように、テストのために別途BabelやWebpackの設定を再現する必要がありません。
  2. ビルドステップの省略: テスト対象のコードやテストコード自体は、Viteの開発サーバーによって変換されますが、テスト実行前に大規模なバンドル処理は行われません。必要なモジュールがオンデマンドでロードされ、実行されます。
  3. ESMネイティブ: VitestはデフォルトでES Modulesを扱います。現代のフロントエンド開発の主流であるESM環境で、余計な変換層を挟まずにテストを実行できるため、効率的です。
  4. 高速なウォッチャーモード: Jestなどでもウォッチャーモードはありますが、VitestはViteのHMRに似た高速なファイル変更検知とテストの再実行を実現します。ファイルを保存した瞬間に、関連するテストがほぼゼロ遅延で再実行されるため、非常にスムーズなテスト駆動開発(TDD)やコード修正時の確認が可能です。これは、テストコードの変更だけでなく、テスト対象のアプリケーションコードの変更に対しても同様に高速です。

「爆速」の具体的なメカニズム

Vitestの「爆速」は、これらの要因が複合的に作用することで実現されます。特にウォッチャーモードでの開発体験が革新的です。

  • Viteによるモジュール解決: テストファイルが import するモジュールは、Viteがキャッシュを使いながら効率的に提供します。これは開発サーバーがブラウザにモジュールを提供するのと似た仕組みです。
  • Worker Threadsによる並列実行: VitestはNode.jsのworker threadsを利用してテストを並列に実行します。これにより、マルチコアCPUの性能を最大限に活かし、テストスイート全体の実行時間を短縮します。
  • V8内蔵のカバレッジツール c8: カバレッジレポートの生成に、Node.jsに組み込まれているV8エンジン自身のカバレッジツール c8(以前はnyc+Istanbulが主流)を利用できます。これにより、追加の計測ライブラリのインストールや設定が不要になり、高速かつ正確なカバレッジ情報を取得できます。(Istanbulも利用可能)

これらのアーキテクチャ的特徴により、Vitestは特に大規模なプロジェクトや、多数のテストファイルを持つプロジェクトにおいて、Jestなどと比較して圧倒的に高速なテスト実行を実現することが多いのです。これは、開発者の待ち時間を減らし、生産性を向上させる上で非常に大きなメリットとなります。

Vitestの主要機能:モダンテストに必要なすべて

Vitestは高速性だけでなく、モダンなJavaScript/TypeScriptテストに求められる豊富な機能を備えています。しかも、その多くがJestとの高い互換性を持つAPIで提供されているため、Jestユーザーであれば学習コストを抑えて導入できます。

1. Viteとのシームレスな連携

これはVitestの根幹をなす機能であり、最大の強みです。

  • Vite設定の継承: プロジェクトルートにある vite.config.js または vite.config.ts を自動的に読み込み、設定を共有します。これにより、以下のような設定がテスト環境にもそのまま適用されます。
    • resolve.alias: パスエイリアス(例: @/components など)
    • plugins: Viteプラグイン(例: @vitejs/plugin-react, @vitejs/plugin-vue など)
    • css.preprocessorOptions: CSSプリプロセッサの設定(Sass, Lessなど)
    • envPrefix: 環境変数のプレフィックス
  • 設定の手間削減: テストのために別途Babel、Webpack、Jest独自の設定ファイルを用意する必要がありません。Viteの設定一つで開発とテストの両方をカバーできます。
  • 一貫したビルドパイプライン: 開発、ビルド、テストのすべてのフェーズでViteのモジュール解決や変換処理が利用されるため、環境差異による問題を減らせます。

2. 高速なテスト実行と優れた開発体験

  • 高速起動と実行: 前述のVite連携による高速性はもちろんです。
  • ウォッチャーモード (vitest watch または vitest): ファイル変更を検知して関連テストのみを高速に再実行します。デフォルトでインタラクティブなCLI UIが起動し、テストの成功/失敗、フィルタリングなどが可能です。
  • 並列実行 (vitest run --threads または --forks): 複数のワーカープロセス/スレッドを使ってテストファイルを並列実行します。デフォルトで有効です。
  • フィルタリング: ファイルパス、テストスイート名、テストケース名などで実行するテストを絞り込めます。
  • UI (Experimental): Webブラウザ上でテスト実行状況を確認できるGUIも実験的に提供されています。

3. TypeScriptネイティブサポート

  • 設定不要: TypeScriptプロジェクトであれば、特別な設定なしに .ts または .tsx ファイルをテストとして記述・実行できます。ViteがTypeScriptを処理します。
  • 型定義: VitestのAPIは型定義ファイル (.d.ts) が整備されており、TypeScript環境での開発効率を高めます。
  • Jest互換の型: Jestからの移行を容易にするため、expect などの型はJestと互換性を持つように設計されています。

4. ES Modules (ESM) サポート

  • デフォルト: VitestはデフォルトでES Modules環境で動作します。現代のJavaScript開発に最適です。
  • CommonJS互換性: ESM環境内でCommonJSモジュールをインポートすることも可能です。(ただし、可能な限りESMへの移行が推奨されます)

5. 強力なモック機能

VitestはJestと非常に互換性の高いモックAPIを提供します。

  • 手動モック (vi.mock): 指定したモジュールの実際のインポートを、定義したダミー実装に置き換えます。ファイル全体、または特定のエクスポートのみをモックできます。
    “`typescript
    // src/utils.ts
    export function fetchData() { // }

    // src/test.ts
    import { fetchData } from ‘./utils’;

    vi.mock(‘./utils’, () => ({
    fetchData: vi.fn(() => ‘mocked data’)
    }));

    test(‘should return mocked data’, () => {
    expect(fetchData()).toBe(‘mocked data’);
    });
    * **スパイ (`vi.spyOn`)**: オブジェクトの既存のメソッドを監視し、呼び出し回数、引数、戻り値などを記録します。元のメソッドはそのまま実行されます。typescript
    const obj = {
    method: () => ‘original’
    };
    const spy = vi.spyOn(obj, ‘method’);

    obj.method();

    expect(spy).toHaveBeenCalledTimes(1);
    expect(spy).toHaveBeenCalledWith();
    * **スタブ (`vi.fn`)**: 新しいモック関数を作成します。呼び出しの記録や、戻り値/実装のカスタマイズが可能です。typescript
    const mockFn = vi.fn();
    mockFn(‘arg1’, ‘arg2’);

    expect(mockFn).toHaveBeenCalledTimes(1);
    expect(mockFn).toHaveBeenCalledWith(‘arg1’, ‘arg2’);
    ``
    * **タイマーモック (
    vi.useFakeTimers)**:setTimeout,setInterval,Date` などの時間関連APIを制御し、非同期処理を含むテストを効率的に記述できます。

6. スナップショットテスト

コンポーネントのレンダリング結果やデータ構造など、オブジェクトの特定の時点での状態を「スナップショット」として保存し、以降のテスト実行時にその状態が変化していないかを検証します。 Jestと互換性のあるAPI (toMatchSnapshot) を使用します。

“`typescript
test(‘my component snapshot’, () => {
// 例えばReactコンポーネントをレンダリングした場合
const component = render();
expect(component.container).toMatchSnapshot();
});

// またはデータ構造
const data = { name: ‘Vitest’, version: ‘1.0.0’ };
expect(data).toMatchSnapshot();
``
スナップショットが期待通りに変化した場合、
-uオプションを付けてテストを実行することでスナップショットを更新できます (vitest run -u`)。

7. カバレッジレポート

テストがコードのどの部分をカバーしているかを示すレポートを生成できます。

  • c8 または istanbul: デフォルトではNode.jsに内蔵されたV8のカバレッジツール c8 を利用します。設定によっては istanbul も利用可能です。c8 はセットアップが簡単で高速なのが利点です。
  • 設定: --coverage オプションを付けてテストを実行することでレポートが生成されます。vitest.config.ts でカバレッジ設定を詳細にカスタマイズできます(対象ファイル、除外ファイル、レポート形式、閾値など)。
    “`typescript
    // vitest.config.ts
    import { defineConfig } from ‘vitest/config’;

    export default defineConfig({
    test: {
    coverage: {
    provider: ‘c8’, // or ‘istanbul’
    reporter: [‘text’, ‘html’], // ‘text’ to console, ‘html’ for interactive report
    include: [‘src//*.{js,ts,jsx,tsx}’],
    exclude: [‘src/main.ts’],
    thresholds: { // 閾値設定
    lines: 80,
    functions: 80,
    branches: 80,
    statements: 80,
    },
    },
    },
    });
    “`
    *
    レポート形式:** テキスト、HTML、lcovonly (CIサービス連携用) など、様々な形式で出力できます。

8. テスト環境 (Environment)

Vitestはテストを実行するJavaScript環境を選択できます。

  • 'node' (デフォルト): Node.js環境でテストを実行します。ファイルの読み書きやネットワークリクエストなど、Node.jsのAPIが利用可能です。サーバーサイドのロジックやユーティリティ関数のテストに適しています。
  • 'jsdom': JSDOMライブラリを使用して、ブラウザのようなDOM環境をNode.js上でエミュレートします。ReactやVueなどのUIコンポーネントのレンダリング結果やDOM操作を伴うコードのテストに適しています。
  • 'happy-dom': JSDOMと同様のDOM環境エミュレーションライブラリです。jsdom よりも高速な場合があるため、大規模なDOMテストで試す価値があります。
  • 設定: vitest.config.ts またはコマンドラインオプションで指定します。
    “`typescript
    // vitest.config.ts
    import { defineConfig } from ‘vitest/config’;

    export default defineConfig({
    test: {
    environment: ‘jsdom’, // or ‘node’, ‘happy-dom’
    },
    });
    “`

9. その他

  • Global API vs Local API: Jestのように describe, it, expect などをグローバルに使用するか、各テストファイルで import { describe, it, expect } from 'vitest' とインポートして使用するかを選択できます。後者はLinterなどとの相性が良い場合があります。
  • In-source Testing (Experimental): テストコードをテスト対象のソースコードと同じファイル内に記述するスタイルをサポートします。(例: // #vitest-include ... のようにコメントでマークアップ)

これらの機能により、Vitestは単なる高速なテストランナーにとどまらず、現代のフロントエンド開発において必要十分なテスト機能を提供しています。Jestからの移行も容易であるため、多くの開発者にとって有力な選択肢となっています。

Vitestの導入と基本的な使い方

Vitestをプロジェクトに導入し、基本的なテストを作成・実行する方法を解説します。

1. Vitestのインストール

Vitestは開発依存としてインストールします。

“`bash

npm

npm install -D vitest

yarn

yarn add -D vitest

pnpm

pnpm add -D vitest
“`

VitestはNode.jsのバージョン14以降が必要です。また、Viteを使用しているプロジェクトであれば、VitestはViteのバージョン3以降を推奨します。

2. Viteプロジェクトでの導入

VitestはViteプロジェクトへの導入が最もシームレスです。特別な設定ファイルは不要で、Viteの vite.config.js/ts をそのまま利用できます。

例えば、以下のような vite.config.ts があるとします。

“`typescript
// vite.config.ts
import { defineConfig } from ‘vite’;
import react from ‘@vitejs/plugin-react’;

export default defineConfig({
plugins: [react()],
resolve: {
alias: {
‘@’: ‘/src’,
},
},
});
“`

Vitestは自動的にこのファイルを読み込み、プラグインやエイリアス設定をテスト実行時にも適用します。

もし、Viteプロジェクトでない場合、またはVitest固有の設定を行いたい場合は、プロジェクトルートに vitest.config.js または vitest.config.ts ファイルを作成します。このファイルもViteの設定ファイル形式と同じです。

“`typescript
// vitest.config.ts
import { defineConfig } from ‘vitest/config’;

export default defineConfig({
test: {
// Vitest固有の設定はここに記述
environment: ‘jsdom’,
globals: true, // describe, it, expectなどをグローバルにするか
},
});
“`

globals: true と設定すると、Jestのようにテストファイル内で describe, it, expect などをインポートせずにそのまま使用できます。インポートして使用したい場合は globals: false とします(推奨)。

3. テストファイルの作成

テストファイルは、通常プロジェクトのどこかに配置し、ファイル名の末尾を .test.js/ts または .spec.js/ts とします。Vitestはデフォルトで src/**/*.test.?(c|m)[jt]s?(x)src/**/*.spec.?(c|m)[jt]s?(x) などのパターンにマッチするファイルをテストファイルとして認識します。

例として、簡単な関数 add.ts をテストしてみましょう。

typescript
// src/add.ts
export function add(a: number, b: number): number {
return a + b;
}

この関数に対するテストファイル src/add.test.ts を作成します。

“`typescript
// src/add.test.ts
// globals: false の場合、以下のようにインポートします
import { describe, it, expect } from ‘vitest’;
import { add } from ‘./add’;

// テストスイートの定義
describe(‘add’, () => {
// テストケースの定義
it(‘should add two numbers’, () => {
const result = add(1, 2);
// アサーション
expect(result).toBe(3);
});

it(‘should handle negative numbers’, () => {
expect(add(-1, -1)).toBe(-2);
expect(add(-1, 1)).toBe(0);
});
});
“`

  • describe: 関連するテストケースをグループ化します。テストスイートと呼ばれます。
  • it (または test): 個々のテストケースを定義します。
  • expect: 検証対象の値を指定します。
  • toBe: 厳密等価 (===) を検証するアサーションマッチャーです。VitestはJestと互換性のある多数のアサーションマッチャー (toEqual, toBeTruthy, toHaveBeenCalled, toMatchSnapshot など) を提供します。

4. テストの実行

テストを実行するには、プロジェクトのルートディレクトリで以下のコマンドを実行します。

“`bash

全てのテストを一度実行

vitest run

ウォッチャーモードで実行 (ファイル変更を検知して再実行)

vitest # デフォルトのコマンド
“`

vitest コマンド(引数なし)はデフォルトでウォッチャーモードで起動します。

ウォッチャーモードでは、以下のようなインタラクティブなCLI UIが表示されます。

“`
RUN v0.34.6 ~/your-project

❯ src/add.test.ts (2 tests)
✔ add > should add two numbers (5ms)
✔ add > should handle negative numbers (1ms)

Test Files 2 passed (2)
Tests 2 passed (2)
Snapshot 0 total
Time 48ms (node: 43ms, workers: 0ms)

PASSED Waiting for file changes…
“`

このUIでは、以下の操作などが可能です。

  • a: 全てのテストを再実行
  • q: ウォッチャーモードを終了
  • p: ファイル名でフィルタリング
  • t: テスト名でフィルタリング
  • u: スナップショットを更新

5. package.jsonへのスクリプト追加

開発を効率化するために、package.jsonscripts にVitestコマンドを追加するのが一般的です。

json
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage"
}
}

これで、npm test (または yarn test, pnpm test) でウォッチャーモードが、npm run test:run で一度きりの実行が、npm run test:coverage でカバレッジレポート付きの実行が可能になります。

基本的な導入と使い方はこれだけです。Viteプロジェクトであれば、npm install -D vitest してテストファイルを書くだけで、すぐにその「爆速」を体験できるでしょう。

より実践的なVitestの使い方

基本を抑えたところで、さらにVitestを効果的に活用するための実践的なテクニックを見ていきましょう。

1. テスト環境の使い分け (Environment)

フロントエンド開発では、Node.js環境とブラウザ環境の両方を考慮する必要があります。Vitestはこれを柔軟に切り替えられます。

  • Node環境 (environment: 'node'):
    • デフォルトです。
    • DOMに依存しないロジック(ユーティリティ関数、データ処理、Node.js APIを利用するコードなど)のテストに適しています。
    • Viteプロジェクトでサーバサイドレンダリング(SSR)に関連するコードのテストにも使えます。
  • JSDOM環境 (environment: 'jsdom'):
    • ブラウザのDOM API、BOM API (window, document など) をNode.js上でエミュレートします。
    • ReactやVue、SvelteなどのUIコンポーネントのレンダリング結果の検証、DOM操作を行うJavaScriptコードのテストに必須です。
  • Happy DOM環境 (environment: 'happy-dom'):
    • jsdom の代替です。場合によっては jsdom よりも高速に動作します。試してみてパフォーマンスが良い方を選択すると良いでしょう。

vitest.config.ts で全体設定を行うのが一般的ですが、特定のテストファイルやテストスイートに対してのみ環境を変更することも可能です。

“`typescript
// vitest.config.ts (全体設定)
import { defineConfig } from ‘vitest/config’;

export default defineConfig({
test: {
environment: ‘jsdom’, // デフォルトをjsdomに
},
});
“`

“`typescript
// src/node-only.test.ts (特定のファイルで上書き)
import { describe, it, expect, vi } from ‘vitest’;

// このファイルだけnode環境で実行
describe.only(‘Node-only tests’, () => {
// vi.setConfig({ environment: ‘node’ }); // もしくはファイル先頭で

it(‘should access Node.js APIs’, () => {
expect(typeof process).toBe(‘object’);
});

// DOM APIは存在しない
it(‘should not have DOM APIs’, () => {
expect(typeof window).toBe(‘undefined’);
expect(typeof document).toBe(‘undefined’);
});
});
``describe.onlyは特定のテストスイートのみを実行するためのVitest/Jestの機能ですが、このように環境指定と組み合わせることもできます(ただしファイル全体の設定はvitest.config.tsまたはコメントによるディレクティブの方が一般的です)。より一般的なファイルごとの環境指定は、vitest.config.tsinclude/exclude` オプションと組み合わせて別の設定ファイルを使ったり、インソーステストのコメントディレクティブなどを使う方法があります。

2. モック機能の応用

VitestのモックはJestとほぼ同じAPIですが、Viteのモジュール解決と連携するため、その挙動を理解することが重要です。

  • vi.mock(modulePath, factory?): モジュール全体または特定のエクスポートをモックします。

    • modulePath: モジュールへのパス(相対パスまたはエイリアスパス)。
    • factory: モックされたモジュールの実装を返す関数。省略すると、モジュール内の関数やクラスが空のモック関数/クラスに置き換えられます。
      “`typescript
      // src/api.ts
      export const fetchData = async () => (await fetch(‘/data’)).json();
      export const postData = async (data: any) => fetch(‘/data’, { method: ‘POST’, body: JSON.stringify(data) });

    // src/service.ts
    import { fetchData, postData } from ‘./api’;

    export async function getData() {
    return await fetchData();
    }

    export async function sendData(data: any) {
    return await postData(data);
    }

    // src/service.test.ts
    import { describe, it, expect, vi } from ‘vitest’;
    import { getData, sendData } from ‘./service’;

    // apiモジュール全体をモック
    vi.mock(‘./api’, () => ({
    fetchData: vi.fn(),
    postData: vi.fn(),
    }));

    // モックされた関数をインポートし直す(重要!)
    const { fetchData, postData } = await import(‘./api’);

    describe(‘service’, () => {
    it(‘getData should call fetchData’, async () => {
    // モックされたfetchDataの戻り値を設定
    (fetchData as vi.Mock).mockResolvedValue(‘mocked data’);

    const result = await getData();
    
    expect(fetchData).toHaveBeenCalledTimes(1);
    expect(result).toBe('mocked data');
    

    });

    it(‘sendData should call postData’, async () => {
    const testData = { id: 1 };
    // モックされたpostDataの戻り値を設定
    (postData as vi.Mock).mockResolvedValue({ ok: true });

    await sendData(testData);
    
    expect(postData).toHaveBeenCalledTimes(1);
    expect(postData).toHaveBeenCalledWith(testData);
    

    });
    });
    ``
    **注意点:**
    vi.mockはテストファイル内のimport文よりも**先に評価されます**。そのため、モックしたモジュールをテストケース内で使用するには、vi.mockの後に再度importし直す(Jestではrequireで行うことが多かった)か、モックファクトリ内でvi.fn()を作成してエクスポートする方法が一般的です。上記の例ではawait import(‘./api’)` を使っていますが、これはESM環境での動的なモックインポートの一般的なパターンです。

  • vi.mocked(mockFn): モック関数にVitestのモックプロパティ(mockClear, mockReset, mockRestore など)や型情報(TypeScriptの場合)を付与します。上記の例で fetchData as vi.Mock とキャストしているのはこのためです。vi.mocked(fetchData) の方がより適切です。

  • 手動モックディレクトリ (__mocks__): Jestと同様に、モジュールと同じ階層に __mocks__ ディレクトリを作成し、モジュールと同名のファイル (api.ts なら __mocks__/api.ts) を配置することで、vi.mock('./api') を呼び出した際に自動的にこのファイルが読み込まれるようにできます。

  • 部分的なモック (vi.importActual): モジュールの一部のエクスポートだけをモックし、それ以外は実際のコードを使いたい場合に便利です。

    “`typescript
    // mocks/api.ts (部分モックの例)
    import { vi } from ‘vitest’;
    const actual = await vi.importActual(‘../src/api’);

    export const fetchData = vi.fn(() => ‘mocked fetch’); // これだけモック
    export const postData = actual.postData; // これは実際のものを使う
    ``
    これで、テストファイルで
    vi.mock(‘./api’)と記述するだけで、fetchDataはモックされ、postData` は実際の関数が使われるようになります。

モックはテストの分離性を高め、外部依存(API呼び出し、データベースアクセス、ファイルの読み書きなど)をシミュレートするために不可欠な機能です。Vitestの強力なモック機能を使いこなすことで、より信頼性の高い単体テストを作成できます。

3. 非同期処理のテスト

JavaScriptでは非同期処理が頻繁に登場します。Vitestは async/await をネイティブにサポートしており、非同期コードのテストも容易です。

“`typescript
// src/asyncUtil.ts
function wait(ms: number): Promise {
return new Promise(resolve => setTimeout(resolve, ms));
}

export async function fetchDataAfterDelay(ms: number): Promise {
await wait(ms);
return ‘data’;
}
“`

“`typescript
// src/asyncUtil.test.ts
import { describe, it, expect, vi } from ‘vitest’;
import { fetchDataAfterDelay } from ‘./asyncUtil’;

describe(‘fetchDataAfterDelay’, () => {
it(‘should fetch data after a delay’, async () => {
// async/await を使用
const result = await fetchDataAfterDelay(100);
expect(result).toBe(‘data’);
});

// タイマーモックと組み合わせて高速化
it(‘should fetch data using fake timers’, async () => {
vi.useFakeTimers(); // タイマーをモック

const promise = fetchDataAfterDelay(1000); // 1000ms待つはずの処理

// 時間を進める
vi.advanceTimersByTime(1000);

// Promiseが解決されるのを待つ
const result = await promise;

expect(result).toBe('data');
vi.useRealTimers(); // タイマーモックを解除

});
});
``asyncキーワードをitまたはtestのコールバック関数に付け、関数内でawaitを使用することで、VitestはPromiseが解決されるのを待ってからテストを終了します。Jestと同様に、コールバック関数にdone引数を受け取る形式(it(‘…’, done => { … }))もサポートしますが、async/await` の使用が推奨されます。

タイマーモック vi.useFakeTimers() は、setTimeout, setInterval, clearTimeout, clearInterval, Date などのAPIを制御し、実際の時間経過を待つことなくテストを実行できます。これは非同期処理を含むテストの実行時間を劇的に短縮するために非常に有効です。

4. UIコンポーネントのテスト

React, Vue, SvelteなどのUIコンポーネントのテストには、environment: 'jsdom' または 'happy-dom' と、それぞれのフレームワークに対応したTesting Libraryライブラリを組み合わせるのが一般的です。

例:React Testing Libraryを使用する場合

“`typescript
// インストール
npm install -D @testing-library/react @testing-library/user-event

vitest.config.ts に environment: ‘jsdom’ を設定

// src/components/Button.tsx
import React from ‘react’;

interface ButtonProps {
onClick: () => void;
children: React.ReactNode;
}

function Button({ onClick, children }: ButtonProps) {
return (

);
}

export default Button;
“`

“`typescript
// src/components/Button.test.tsx
import { describe, it, expect, vi } from ‘vitest’;
import { render, screen } from ‘@testing-library/react’;
import userEvent from ‘@testing-library/user-event’;
import Button from ‘./Button’;

describe(‘Button’, () => {
it(‘renders with children’, () => {
render();
// screen.getByText は DOM 内のテキストで要素を検索
expect(screen.getByText(‘Click Me’)).toBeInTheDocument();
});

it(‘calls onClick when clicked’, async () => {
const handleClick = vi.fn(); // モック関数を作成
render();

// userEvent はユーザー操作をシミュレート
await userEvent.click(screen.getByText('Click Me'));

expect(handleClick).toHaveBeenCalledTimes(1); // ボタンがクリックされたか検証

});
});
``
*
@testing-library/react: Reactコンポーネントをレンダリングするためのユーティリティを提供します。DOM要素のクエリ方法 (screen.getByRole,screen.getByTextなど) は、ユーザーが操作するのと同じように要素を見つけ出すことに重点を置いており、テストの保守性を高めます。
*
@testing-library/user-event`: ユーザーの実際の操作(クリック、入力、キーボード操作など)をより忠実にシミュレートします。

Vue Test Utils (@vue/test-utils) や Svelte Testing Library (@testing-library/svelte) など、他のフレームワークにも対応ライブラリがあり、同様の方法でVitestと組み合わせて使用できます。

5. グローバルセットアップ

全てのテストファイルが実行される前に共通のセットアップコードを実行したい場合があります(例: JSDOM環境でのDOMクリーニング、テストデータベースの初期化、モックの定義など)。これは setupFiles または setupFilesAfterEnv オプションで指定できます。

  • setupFiles: 各テストファイルが実行されるに実行されます。Node.js環境でモックやグローバル変数などを設定する場合に使用できます。
  • setupFilesAfterEnv: テスト環境(JSDOMなど)がセットアップされた、かつ各テストファイルが実行されるに実行されます。UIコンポーネントテスト用のDOM操作ユーティリティ(例: @testing-library/jest-domtoBeInTheDocument などのマッチャー)を追加する場合に適しています。

例:React Testing Library用のセットアップ

“`typescript
// vitest.config.ts
import { defineConfig } from ‘vitest/config’;

export default defineConfig({
test: {
environment: ‘jsdom’,
setupFiles: ‘./test/setup.ts’, // セットアップファイル指定
},
});
“`

typescript
// test/setup.ts
import '@testing-library/jest-dom/vitest'; // Jest DOMマッチャーをVitestに登録
// その他、共通で必要なモックや設定などを記述

setupFilessetupFilesAfterEnv に指定したファイルは、テストランナーによって内部的に各テストファイルの前にインポートされます。

6. インソーステスト (In-source Testing)

これはまだ実験的な機能ですが、テストコードをテスト対象のソースコードと同じファイル内に記述できるスタイルです。

“`typescript
// src/math.ts
export function sum(a: number, b: number): number {
return a + b;
}

// In-source Test (コメントでマークアップ)
if (import.meta.vitest) { // Vitest環境でのみ実行されるガード
const { it, expect } = import.meta.vitest;

it(‘sum’, () => {
expect(sum(1, 1)).toBe(2);
});
}
``import.meta.vitestはVitestが提供する特殊なオブジェクトで、Vitest環境でのみ真となり、it,expectなどが含まれています。この機能を使うには、vitest.config.ts` で設定が必要です。

“`typescript
// vitest.config.ts
import { defineConfig } from ‘vitest/config’;

export default defineConfig({
test: {
includeSource: [‘src/*/.{js,ts}’], // インソーステストを有効にするファイルパターン
},
});
“`
インソーステストは、小規模なユーティリティ関数や、コードとテストが非常に密接に関連している場合に、コードの近くにテストを置くことで可読性やメンテナンス性を向上させる可能性があります。ただし、テストコードがアプリケーションコードに混ざることになるため、チームやプロジェクトの方針に合わせて慎重に採用を検討する必要があります。

これらの実践的な使い方をマスターすることで、Vitestを使ったテスト開発の効率と信頼性をさらに向上させることができます。

Vitestの高度な設定とCI/CD連携

Vitestは柔軟な設定オプションを提供しており、CI/CDパイプラインとの連携も容易です。

1. vitest.config.ts 詳細

Vitestの設定は vitest.config.ts (または .js) の test プロパティに記述します。

“`typescript
// vitest.config.ts
import { defineConfig } from ‘vitest/config’;

export default defineConfig({
test: {
// テストファイルのパターン (glob)
include: [‘src/*/.{test,spec}.?(c|m)[jt]s?(x)’],
exclude: [‘node_modules’, ‘dist’, ‘.idea’, ‘.git’, ‘.cache’, ‘tests/e2e’],

// テスト環境
environment: 'jsdom', // 'node', 'happy-dom'

// APIをグローバルに公開するか (true: Jestライク, false: インポート必須)
globals: false,

// 各テストファイル実行前に読み込むセットアップファイル
setupFiles: ['./test/setup.ts'],
// テスト環境セットアップ後に読み込むファイル
setupFilesAfterEnv: [], // setupFiles とほぼ同じだがタイミングが微妙に異なる

// モック設定
mockReset: true, // 各テスト後にモックの状態をリセットするか
clearMocks: true, // 各テスト後にモックの呼び出し履歴などをクリアするか
restoreMocks: true, // 各テスト後に vi.spyOn で作成したスパイを元の状態に戻すか

// カバレッジ設定
coverage: {
  provider: 'c8', // 'c8' or 'istanbul'
  reporter: ['text', 'html', 'lcovonly'],
  include: ['src/**/*.{js,ts,jsx,tsx}'],
  exclude: ['src/main.ts'],
  thresholds: { global: { branches: 80, functions: 80, lines: 80, statements: 80 } },
},

// テスト実行プール設定
pool: 'threads', // 'threads' (worker_threads) or 'forks' (child_process)
poolOptions: {
  threads: {
    singleThread: false, // false: 並列実行, true: シングルスレッド (デバッグ用)
    isolate: true, // true: 各テストファイルを独立したVMで実行
  },
  forks: {
    singleFork: false,
  }
},

// タイムアウト設定 (ms)
testTimeout: 5000,
hookTimeout: 10000, // before/afterAll, before/afterEach などのフックのタイムアウト

// ウォッチャーモードでの挙動
watchExclude: ['node_modules', 'dist'], // ウォッチ対象から除外するファイル

// 環境変数
env: {
  // テスト実行時に設定したい環境変数
  MY_TEST_ENV: 'test_value'
},

// CSS処理
css: {
  // Vitest実行時にCSSをどう扱うか
  // true: CSSモジュールを空のオブジェクトに解決
  // 'inline': <style> タグとしてインライン化
  // false: 何もしない
  modules: {
    classNameTransformation: 'camelCaseOnly' // CSSモジュールクラス名の変換
  }
},

// Vite開発サーバー設定
server: {
  // Viteサーバーをどのように扱うか
  deps: {
    inline: [], // 常にバンドルする依存関係 (ESM対応していないライブラリなど)
  },
},

// In-source Testing
includeSource: ['src/**/*.{js,ts}'],

// レポーター
reporters: 'default', // 'default', 'verbose', 'dot', 'json', etc.

},
});
``
主要な設定オプションを網羅しました。これらの設定を適切に行うことで、プロジェクトの構造や要件に合わせてVitestの挙動を細かく制御できます。特に
coveragepoolOptionsmockReset` などの設定は、CI環境でのテスト実行時間や安定性に影響するため重要です。

2. CI/CDとの連携

VitestはCLIベースで実行できるため、GitHub Actions, GitLab CI, CircleCIなどの各種CI/CDサービスと簡単に連携できます。

基本的なCI設定は以下のようになります。

“`yaml

.github/workflows/test.yml (GitHub Actions の例)

name: Run Tests

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest # または他のOS

steps:
  - uses: actions/checkout@v3

  - name: Set up Node.js
    uses: actions/setup-node@v3
    with:
      node-version: '18' # プロジェクトで使用しているNode.jsバージョン

  - name: Install dependencies
    run: npm install # または yarn install, pnpm install

  - name: Run Vitest tests
    # --run は一度きりの実行、 --coverage でカバレッジレポートを生成
    # lcovonly はカバレッジサービスにアップロードするための形式
    run: npm run test:coverage -- --coverage.reporter=lcovonly # または vitest run --coverage --coverage.reporter=lcovonly

  - name: Upload coverage to Codecov # 例: Codecov を利用する場合
    uses: codecov/codecov-action@v3
    with:
      files: ./coverage/lcov.info # Vitestのデフォルト出力パス
      fail_ci_if_error: true # エラーが発生した場合にCIを失敗させる
      verbose: true # 詳細ログ出力

“`

このワークフローでは、
1. コードをチェックアウトし、
2. Node.js環境をセットアップし、
3. 依存関係をインストールし、
4. vitest run --coverage --coverage.reporter=lcovonly コマンドでテストを実行し、カバレッジレポートを lcovonly 形式で生成します。
5. 生成された lcov.info ファイルをCodecovなどのカバレッジ集計サービスにアップロードします。(Codecov以外にもCode Climate, Coverallsなど様々なサービスがあります)

vitest.config.tscoverage.reporter['text', 'lcovonly'] のように複数指定することも可能です。

CI環境での高速化の工夫:

  • 並列実行: CI環境は通常マルチコアのため、並列実行 (--threads または --forks)が有効になっていることを確認します。Vitestはデフォルトで有効です。
  • キャッシュ: Node.jsモジュールやViteのキャッシュをCI環境でキャッシュすることで、依存関係のインストールや初回実行を高速化できます。GitHub Actionsの actions/cache などの機能を利用します。
    “`yaml

    • name: Cache dependencies
      uses: actions/cache@v3
      with:
      path: ~/.npm # または ~/.yarn/cache, ~/.pnpm-store
      key: ${{ runner.os }}-node-${{ hashFiles(‘**/package-lock.json’) }} # または yarn.lock, pnpm-lock.yaml
      restore-keys: |
      ${{ runner.os }}-node-
      “`
  • 特定のテストのみ実行: Pull Requestのマージ元ブランチからの変更ファイルに関連するテストのみを実行するようにCIを設定することで、テスト時間を短縮できる場合があります。(Vitest自体に強力なファイル変更検知機能がありますが、CIの初回実行などで有効な場合があります)

3. パフォーマンスチューニング

Vitestの高速性は大きな魅力ですが、プロジェクトの規模や複雑さによっては、さらにパフォーマンスを最適化したい場合があります。

  • 並列実行数の調整: デフォルトの並列実行数が環境に合わない場合、vitest run --threads=N (または --forks=N) のようにワーカー数を手動で調整することで最適な並列度を見つけることができるかもしれません。vitest.config.tstest.poolOptions でも設定可能です。
  • モックの最適化: 不要なモックはパフォーマンスを低下させる可能性があります。必要なモジュールや関数のみをモックするようにします。また、vi.mock のファクトリ関数は、モックされるモジュールをインポートする前に評価されるため、複雑な処理や重いインポートを含まないように注意が必要です。
  • inline オプションの調整: vitest.config.tsserver.deps.inline オプションは、特定の依存関係をViteが事前にバンドルするよう指示します。これは、ESM対応していない古いライブラリをテストする場合などに必要ですが、多すぎるとVitestの高速性(オンデマンド解決)が損なわれる可能性があります。必要な最小限のライブラリに限定します。
  • テスト環境の選択: jsdomhappy-dom はネイティブNode.js環境よりもオーバーヘッドがあります。DOMが必要ないテストは node 環境で実行するように、ファイルを分割したり設定を調整したりします。
  • 大きなテストスイートの分割: 一つのテストファイルがあまりに多くのテストケースを含むと管理や並列実行の効率が悪くなることがあります。関連性の高いテストケースごとにファイルを分割することを検討します。

Vitestのメリット・デメリット

Vitestを採用する際に考慮すべきメリットとデメリットを整理します。

メリット

  1. 圧倒的な高速性: Viteとの連携によるビルドレス開発の概念を持ち込むことで、特にウォッチャーモードでのテスト実行が非常に高速です。これは開発体験を劇的に向上させます。
  2. Vite設定の継承: vite.config.ts をそのまま利用できるため、重複した設定が不要で、セットアップが簡単です。パスエイリアスやViteプラグインなどがテスト環境にも適用されるのは大きな利点です。
  3. 優れたTypeScriptサポート: ネイティブでTypeScriptをサポートしており、追加の設定なしに .ts/.tsx ファイルをテストできます。型定義も整備されています。
  4. Jest互換性の高さ: describe, it, expect, vi.mock, vi.spyOn, スナップショットテストなど、主要なAPIがJestと非常に高い互換性を持っています。Jestからの移行が比較的容易です。
  5. 優れた開発体験: インタラクティブなCLI UI、高速なウォッチャーモード、クリアなエラーレポートなど、開発者がテストを書き、実行し、デバッグしやすいように設計されています。
  6. ESMネイティブ: 現代のJavaScript開発の主流であるESM環境で効率的に動作します。
  7. モダンな設計: V8カバレッジ (c8)、Worker Threadsなど、モダンな技術を利用しており、将来性があります。

デメリット

  1. 比較的新しいフレームワーク: Jestなどに比べると歴史が浅く、コミュニティの規模や情報量はまだ追いついていません。エッジケースや特定のライブラリとの連携で情報が見つけにくい場合があります。
  2. Viteへの依存: Viteプロジェクトで最もスムーズに導入できます。Viteを使っていないプロジェクトでもVitestを導入することは可能ですが、Viteを内部的にインストール・設定する必要があり、導入のハードルが少し上がります。
  3. 特定のJest機能との非互換性: ほとんどの主要機能は互換性がありますが、Jestの特定の高度な機能(例: Custom Environmentの実装の細かな違い、特定のGlobal Mockなど)は完全に互換性がない場合があります。移行時に注意が必要です。
  4. ドキュメントの成熟度: ドキュメントは非常に分かりやすく書かれていますが、網羅性や特定のユースケースに関する情報が、成熟したフレームワークに比べてまだ発展途上な部分があるかもしれません。

全体として、Vitestのメリットはデメリットを大きく上回ることが多いと言えます。特にViteを採用しているプロジェクトにとっては、Vitestは最も有力なテストフレームワークの選択肢となるでしょう。

Vitest vs Jest vs Mocha vs etc. 他フレームワークとの比較

Vitestを他の主要なJavaScriptテストフレームワークと比較してみましょう。

特徴 Vitest Jest Mocha Jasmine
開発元 Vite コアチーム Facebook (現 Meta) オープンソース オープンソース
基盤/アーキテクチャ Vite 開発サーバー、ESMネイティブ JSDOMベース (Node環境も可)、トランスパイル要 Node.jsベース、環境・アサーションは別途 独自ランナー、JSDOMベース (Node環境も可)
高速性 非常に高速 (特にウォッチャー) 中程度〜高速 中程度 中程度
セットアップ Vite と連携し容易 設定ファイル必要、 Babel など連携必要 ランナーのみ、 Babel/Webpack など連携必要 設定ファイル必要
TypeScript ネイティブサポート Babel 連携必要 (ts-jestなど) Babel 連携必要 Babel 連携必要
ESM サポート ネイティブ 設定が必要 (experimental) 設定が必要 設定が必要
モック機能 内蔵 (Jest互換) 内蔵 (強力) 別途ライブラリ (sinonなど) 必要 内蔵
アサーション 内蔵 (Jest互換 expect) 内蔵 (expect) 別途ライブラリ (chaiなど) 必要 内蔵 (expect)
カバレッジ 内蔵 (c8/istanbul) 内蔵 (istanbul) 別途ライブラリ (nyc/istanbul) 必要 別途ライブラリが必要
スナップショット 内蔵 (Jest互換) 内蔵 なし (別途ライブラリ) なし (別途ライブラリ)
DOM 環境 jsdom/happy-dom を選択 jsdom 内蔵 別途ライブラリ (jsdomなど) 必要 jsdom 内蔵
コミュニティ 成長中 非常に大きい 大きい 中程度
学習コスト Jest ユーザーなら低い、Vite ユーザーなら低い 広く使われているため情報豊富 Minimal なため柔軟だが自分で組み合わせる シンプルで学習しやすい

各フレームワークの簡単な特徴とVitestとの比較

  • Jest: 現在最も広く使われているJavaScriptテストフレームワークです。アサーション、モック、カバレッジ、スナップショットなど、テストに必要な機能がオールインワンで提供されており、セットアップも比較的容易です。しかし、大規模なプロジェクトではテスト実行時間が課題となることがあります。VitestはJestの強力な機能を維持しつつ、Viteの高速性を取り入れた「Jestの進化版」と位置づけることができます。Jestからの移行はAPI互換性が高いため比較的容易です。
  • Mocha: 非常にシンプルで柔軟なテストランナーです。アサーションライブラリ(Chaiなど)、モックライブラリ(Sinonなど)、カバレッジツール(Istanbul/nycなど)を自分で組み合わせて使用します。自由度が高い反面、セットアップに手間がかかります。VitestはMochaとは異なり、Jestのようにオールインワンの機能を提供しますが、基盤となるランナーはMochaと同様にシンプルで高速な設計思想を持っています。
  • Jasmine: Mochaと同様に、アサーションやモック機能を内蔵しているオールインワン型のフレームワークです。フレームワーク独自の構文(BDDスタイル)を持ちます。VitestやJestと比較すると、最近は採用されるケースが減ってきている印象です。

Vitestを選ぶべきケース

  • Vite を使っているプロジェクト: Vitestの最大のメリットであるVite連携を最大限に活かせます。導入も非常に簡単です。
  • テスト実行時間を短縮したい: Jestなどでテスト時間が課題になっているプロジェクトであれば、Vitestへの移行でパフォーマンスが大幅に改善される可能性があります。
  • TypeScript を積極的に使っている: ネイティブサポートにより、TypeScript環境での開発体験が優れています。
  • モダンな開発環境が好き: ESMネイティブ、新しい技術の採用など、モダンな設計思想に魅力を感じる場合にフィットします。
  • Jest に慣れている: Jestと似たAPIを提供するため、Jestユーザーであればスムーズに移行できます。

他のフレームワークを選ぶべきケース

  • Vite を全く使う予定がないプロジェクト: Vitestの導入は可能ですが、Viteを内部的にインストール・設定する必要があり、メリットが半減します。
  • 特定の Jest の高度な機能に強く依存している: まれに Vitest がサポートしていない Jest の機能がある可能性があります。移行前に確認が必要です。
  • フレームワークの安定性や長い実績を最も重視する: Jest は歴史が長く、様々な環境での実績が豊富です。
  • テスト環境を完全に自分でコントロールしたい: Mochaのように、テストランナーだけを使って、必要なライブラリを全て自分で選びたい場合は、Mochaの方が柔軟性が高いかもしれません。

結論として、Viteを使っているプロジェクト、あるいはテスト実行速度に課題を感じているJestユーザーであれば、Vitestは最も魅力的な選択肢となるでしょう。その爆速性とJest互換性の高さは、テスト開発のワークフローを大きく改善する可能性を秘めています。

まとめと今後の展望

この記事では、爆速テストフレームワークVitestについて、その「爆速」の秘密であるVite連携のアーキテクチャから、主要機能、導入・使い方、実践的なテクニック、高度な設定、CI/CD連携、そして他のフレームワークとの比較まで、徹底的に解説しました。

Vitestは、Viteの開発サーバーを再利用することで、従来のJavaScriptテストフレームワークのボトルネックとなっていたビルドやモジュール解決のオーバーヘッドを劇的に削減しました。特に、ウォッチャーモードでの瞬時のテスト再実行は、テスト駆動開発(TDD)やリファクタリングのサイクルを加速させ、開発者の生産性と開発体験を大きく向上させます。

Jestとの高い互換性を持つAPI、ネイティブなTypeScriptサポート、強力なモック・スナップショット機能、柔軟なテスト環境選択など、Vitestはモダンなフロントエンドテストに必要な機能を包括的に提供しています。Viteエコシステムの一部として設計されているため、Viteユーザーにとっては最も自然で効率的なテストソリューションと言えるでしょう。

もちろん、Vitestはまだ比較的新しいフレームワークであり、Jestなどの成熟したフレームワークに比べてコミュニティの規模や情報の蓄積では及ばない点もあります。しかし、その革新性と高速性から、Vue.jsの公式テストフレームワークとして採用されたり、多くのOSSプロジェクトや企業で導入が進んでおり、急速にエコシステムが拡大しています。

今後のVitestは、さらなるパフォーマンス改善、機能拡充(例えば、より高度なブラウザテストサポートなど)、そしてコミュニティの成長を通じて、JavaScript/TypeScriptテストフレームワークの新たなデファクトスタンダードとなる可能性を秘めています。

もしあなたがViteを使っている、またはテスト実行時間の遅さに悩んでいるなら、ぜひVitestを試してみてください。その「爆速」体験は、きっとあなたのテストワークフローに革命をもたらすはずです。

この記事が、あなたがVitestを理解し、導入し、日々の開発で活用するための一助となれば幸いです。爆速テスト環境で、より高品質でメンテナンスしやすいソフトウェア開発を目指しましょう!

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール