Vitest の使い方完全ガイド — Vite時代の次世代テストフレームワーク
一言でいうと
VitestはViteのエコシステム上に構築された高速なJavaScript/TypeScriptテストフレームワークです。Jestとほぼ互換のAPIを持ちながら、Viteのトランスフォームパイプラインとホットモジュールリロード(HMR)を活用することで、圧倒的な実行速度を実現します。
どんな時に使う?
- Viteベースのプロジェクトにテストを導入したい時 —
vite.config.tsの設定(エイリアス、プラグインなど)をそのままテストでも共有できるため、設定の二重管理が不要になります。 - Jestの遅さに不満がある時 — ESM対応やTypeScriptのトランスパイルでJestの設定に苦労している場合、Vitestに移行するだけで設定がシンプルになり、実行速度も大幅に改善します。
- コンポーネントテスト・ユニットテストを高速に回したい時 — ウォッチモードがデフォルトで有効になっており、変更されたファイルに関連するテストだけを再実行するため、開発中のフィードバックループが極めて短くなります。
インストール
# npm
npm install -D vitest
# yarn
yarn add -D vitest
# pnpm
pnpm add -D vitest
Note: この記事はVitest v4.x系を前提としています。バージョンによってAPIや挙動が異なる場合があります。公式ドキュメント(https://vitest.dev/)で最新情報を確認してください。
基本的な使い方
最小構成
package.json にスクリプトを追加します。
{
"scripts": {
"test": "vitest",
"test:run": "vitest run"
}
}
vitest コマンドはデフォルトでウォッチモードで起動します。CI環境などで一度だけ実行したい場合は vitest run を使います。
設定ファイル
vite.config.ts にテスト設定を追加するか、専用の vitest.config.ts を作成します。
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true, // describe, it, expect をimportなしで使用
environment: 'node', // 'jsdom' | 'happy-dom' | 'node'
include: ['src/**/*.{test,spec}.{ts,tsx}'],
coverage: {
provider: 'v8', // 'v8' | 'istanbul'
reporter: ['text', 'json', 'html'],
},
},
});
はじめてのテスト
// src/utils/math.ts
export function add(a: number, b: number): number {
return a + b;
}
export function divide(a: number, b: number): number {
if (b === 0) throw new Error('Division by zero');
return a / b;
}
// src/utils/math.test.ts
import { describe, it, expect } from 'vitest';
import { add, divide } from './math';
describe('math utils', () => {
it('should add two numbers', () => {
expect(add(1, 2)).toBe(3);
});
it('should divide two numbers', () => {
expect(divide(10, 2)).toBe(5);
});
it('should throw on division by zero', () => {
expect(() => divide(10, 0)).toThrowError('Division by zero');
});
});
npx vitest run
よく使うAPI — Vitestの使い方を深掘り
1. vi.fn() — モック関数の作成
Jestの jest.fn() に相当します。関数の呼び出し回数や引数を追跡できます。
import { describe, it, expect, vi } from 'vitest';
describe('vi.fn()', () => {
it('should track calls', () => {
const mockFn = vi.fn((x: number) => x * 2);
mockFn(3);
mockFn(5);
expect(mockFn).toHaveBeenCalledTimes(2);
expect(mockFn).toHaveBeenCalledWith(3);
expect(mockFn.mock.results[0].value).toBe(6);
});
});
2. vi.mock() — モジュールモック
外部モジュールやプロジェクト内モジュールを丸ごとモックに差し替えます。
import { describe, it, expect, vi } from 'vitest';
// モジュール全体をモック
vi.mock('./api', () => ({
fetchUser: vi.fn().mockResolvedValue({ id: 1, name: 'Alice' }),
}));
import { fetchUser } from './api';
describe('vi.mock()', () => {
it('should return mocked user', async () => {
const user = await fetchUser(1);
expect(user).toEqual({ id: 1, name: 'Alice' });
expect(fetchUser).toHaveBeenCalledWith(1);
});
});
3. vi.spyOn() — 既存オブジェクトのメソッドを監視
元の実装を保持しつつ、呼び出しを追跡できます。
import { describe, it, expect, vi, afterEach } from 'vitest';
describe('vi.spyOn()', () => {
afterEach(() => {
vi.restoreAllMocks();
});
it('should spy on console.log', () => {
const spy = vi.spyOn(console, 'log').mockImplementation(() => {});
console.log('hello');
expect(spy).toHaveBeenCalledWith('hello');
});
});
4. vi.useFakeTimers() / vi.advanceTimersByTime() — タイマーの制御
setTimeout や setInterval をテスト内で自在に操作できます。
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
function delayedGreeting(callback: () => void) {
setTimeout(callback, 3000);
}
describe('Fake Timers', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('should call callback after 3 seconds', () => {
const callback = vi.fn();
delayedGreeting(callback);
expect(callback).not.toHaveBeenCalled();
vi.advanceTimersByTime(3000);
expect(callback).toHaveBeenCalledOnce();
});
});
5. test.each() — パラメータ化テスト
同じテストロジックを複数のデータセットで実行します。
import { describe, it, expect } from 'vitest';
import { add } from './math';
describe('add - parameterized', () => {
it.each([
[1, 2, 3],
[0, 0, 0],
[-1, 1, 0],
[100, 200, 300],
])('add(%i, %i) should return %i', (a, b, expected) => {
expect(add(a, b)).toBe(expected);
});
});
類似パッケージとの比較
| 特徴 | Vitest | Jest | Mocha + Chai |
|---|---|---|---|
| 実行速度 | ◎ 非常に高速 | △ 大規模で遅くなりがち | ○ 普通 |
| TypeScript対応 | ◎ ゼロコンフィグ | △ ts-jest等が必要 | △ 別途設定が必要 |
| ESMサポート | ◎ ネイティブ | △ 実験的 | ○ 対応 |
| Vite設定の共有 | ◎ そのまま使える | ✕ 不可 | ✕ 不可 |
| Jest互換API | ◎ ほぼ完全互換 | — | ✕ 別体系 |
| エコシステム | ○ 急速に拡大中 | ◎ 最大規模 | ○ 成熟 |
| インブラウザテスト | ○ 対応 | ✕ jsdomのみ | △ Karma等が必要 |
| スナップショットテスト | ◎ 対応 | ◎ 対応 | △ プラグインが必要 |
選定の目安:
- Viteを使っているなら → Vitest 一択
- レガシーなWebpackプロジェクトで移行コストを避けたい → Jest
- 最小限の構成で柔軟に組みたい → Mocha + Chai
注意点・Tips
1. globals: true 使用時は型定義を追加する
globals: true を設定すると describe や it をimportなしで使えますが、TypeScriptの型エラーを防ぐために tsconfig.json に型定義を追加してください。
{
"compilerOptions": {
"types": ["vitest/globals"]
}
}
2. Jestからの移行は段階的に
VitestはJestとほぼ互換のAPIを持ちますが、以下の点に注意が必要です。
jest.fn()→vi.fn()に置換jest.mock()→vi.mock()に置換@jest/globalsのimportをvitestに変更moduleNameMapper→ Viteのresolve.aliasに移行
公式のマイグレーションガイドを参照してください。
3. テストの並列実行に注意
Vitestはデフォルトでテストファイルを並列実行します。グローバルな状態(DB、ファイルシステムなど)を共有するテストでは競合が発生する可能性があります。
// 特定のファイルを直列実行にする
// vitest.config.ts
export default defineConfig({
test: {
sequence: {
concurrent: false, // ファイル内のテストを直列実行
},
// または特定のファイルだけ pool の設定で制御
},
});
テスト内で明示的に直列実行を指定することもできます。
describe.sequential('DB tests', () => {
it('should insert record', async () => { /* ... */ });
it('should read record', async () => { /* ... */ });
});
4. カバレッジの取得
カバレッジプロバイダーを別途インストールする必要があります。
# v8プロバイダー(推奨・高速)
npm install -D @vitest/coverage-v8
# istanbulプロバイダー
npm install -D @vitest/coverage-istanbul
npx vitest run --coverage
5. UI モードで視覚的にデバッグ
npm install -D @vitest/ui
npx vitest --ui
ブラウザ上でテスト結果をインタラクティブに確認でき、失敗したテストの原因特定が格段に楽になります。
まとめ
VitestはViteの高速なビルドパイプラインを活かした次世代テストフレームワークで、Jest互換のAPIにより学習コストを最小限に抑えながら、ESM・TypeScriptへのネイティブ対応と圧倒的な実行速度を提供します。Viteベースのプロジェクトであれば設定をそのまま共有できるため、導入の手間もほぼありません。新規プロジェクトはもちろん、Jestからの移行先としても最有力の選択肢です。