vitest について
JavaScript または TypeScript の開発者向けに、Vitest のテスト作成と保守をまとめたリファレンススキルです。テスト構成、モック、カバレッジ、CI、トラブルシューティングを扱います。
主な機能
- 01テストスイートとアサーション
- 02関数とモジュールのモック
- 03非同期テストのパターン
- 04カバレッジと CI の設定
- 05テスト構成とトラブルシューティング
活用シーン
- 01アプリケーションコードの単体テスト作成
- 02API モジュールと関数のモック
- 03CI 向けカバレッジレポートの設定
MCP 対応
包括的な Vitest ドキュメントの参照先として `mcp__documentation__fetch_docs` を案内します。
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill vitestインストール後、Claude Code で/vitestを入力して読み込まれたか確認します。
同梱ファイルquick-ref/coverage.mdquick-ref/mocking.md
詳細
Vitest コアの知識
深い知識:包括的なドキュメントを参照するには、テクノロジーに
vitestを指定してmcp__documentation__fetch_docsを使用してください。
このスキルを使用しない場合
- E2E テスト - ブラウザーを使ったエンドツーエンドテストには
playwrightまたはcypressを使用してください - React コンポーネントのテスト - コンポーネントのテストには、このスキルと
testing-libraryを組み合わせてください - Jest からの移行 - プロジェクトで Jest を使用している場合は、代わりに
jestスキルを使用してください(構文は似ていますが、完全に同じではありません) - API 統合テスト - フレームワーク固有の統合テストスキル(spring-boot-integration など)を使用してください
基本的なテスト
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { calculateTotal } from './utils';
describe('calculateTotal', () => {
it('should sum array of numbers', () => {
expect(calculateTotal([1, 2, 3])).toBe(6);
});
it('should return 0 for empty array', () => {
expect(calculateTotal([])).toBe(0);
});
});
マッチャー
// Equality
expect(value).toBe(exact);
expect(value).toEqual(deepEqual);
expect(value).toMatchObject(partial);
// Truthiness
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeDefined();
// Numbers
expect(value).toBeGreaterThan(n);
expect(value).toBeCloseTo(0.3, 5);
// Strings
expect(str).toMatch(/pattern/);
expect(str).toContain('substring');
// Arrays
expect(arr).toContain(item);
expect(arr).toHaveLength(3);
// Errors
expect(() => fn()).toThrow();
expect(() => fn()).toThrowError('message');
モック
// Mock function
const mockFn = vi.fn();
mockFn.mockReturnValue(42);
mockFn.mockResolvedValue({ data: [] });
// Mock module
vi.mock('./api', () => ({
fetchUsers: vi.fn().mockResolvedValue([])
}));
// Spy
const spy = vi.spyOn(object, 'method');
expect(spy).toHaveBeenCalledWith('arg');
// Reset
vi.clearAllMocks();
vi.resetAllMocks();
非同期テスト
it('should fetch data', async () => {
const data = await fetchUsers();
expect(data).toHaveLength(3);
});
it('should reject', async () => {
await expect(fetchFail()).rejects.toThrow('Error');
});
設定
// vitest.config.ts
export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
coverage: { provider: 'v8', reporter: ['text', 'html'] }
}
});
本番環境への対応
テストの構成
// Proper test structure
describe('UserService', () => {
// Setup/teardown at appropriate level
let service: UserService;
let mockDb: MockedObject<Database>;
beforeAll(async () => {
// Expensive setup once
await setupTestDatabase();
});
beforeEach(() => {
// Reset state before each test
mockDb = vi.mocked(new Database());
service = new UserService(mockDb);
vi.clearAllMocks();
});
afterAll(async () => {
// Cleanup
await teardownTestDatabase();
});
describe('create', () => {
it('should create user with valid data', async () => {
// Arrange
const input = { name: 'John', email: '[email protected]' };
mockDb.insert.mockResolvedValue({ id: '1', ...input });
// Act
const result = await service.create(input);
// Assert
expect(result).toMatchObject(input);
expect(mockDb.insert).toHaveBeenCalledWith('users', input);
});
it('should throw on duplicate email', async () => {
mockDb.insert.mockRejectedValue(new UniqueConstraintError());
await expect(service.create(input)).rejects.toThrow('Email already exists');
});
});
});
カバレッジの設定
// vitest.config.ts
export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'],
exclude: [
'node_modules/',
'test/',
'**/*.d.ts',
'**/*.config.*',
'**/types/**',
],
thresholds: {
lines: 80,
functions: 80,
branches: 75,
statements: 80,
},
},
},
});
CI の設定
# GitHub Actions
- name: Run tests
run: npm run test:ci
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
fail_ci_if_error: true
// package.json scripts
{
"scripts": {
"test": "vitest",
"test:ci": "vitest run --coverage --reporter=junit --outputFile=test-results.xml",
"test:watch": "vitest --watch"
}
}
モニタリング指標
| 指標 | 目標 |
|---|---|
| 行カバレッジ | > 80% |
| 分岐カバレッジ | > 75% |
| テスト実行時間 | < 60s |
| 不安定なテストの割合 | 0% |
チェックリスト
- Arrange-Act-Assert パターン
- 分離されたテスト(共有状態なし)
- 意味のあるテストの説明
- カバレッジのしきい値を適用
- CI/CD との統合
- テスト内で console.log を使用しない
- テスト間でモックをリセット
- 非同期テストで適切に await
- エッジケースを網羅
- エラー経路をテスト
アンチパターン
| アンチパターン | 問題点 | 解決策 |
|---|---|---|
| 実装の詳細をテストする | リファクタリングでテストが壊れます | 内部実装ではなく、振る舞いをテストします |
| テスト間で状態を共有する | 不安定で、実行順に依存するテストになります | beforeEach を使用し、セットアップを分離します |
| 根拠のない待機(setTimeout) | テストが遅く、不安定になります | waitFor または非同期ユーティリティを使用します |
| モックをリセットしない | 前のテストの影響が次のテストに及びます | beforeEach で vi.clearAllMocks() を呼び出します |
| プライベートメソッドをテストする | 実装との結びつきが強くなります | 公開 API を通じてテストします |
| 巨大なテストを 1 つだけ作成する | 失敗のデバッグが難しくなります | できれば、テストごとにアサーションを 1 つにします |
| エラーケースのテストがない | 本番環境でバグを見逃します | 正常系とエラー経路の両方をテストします |
トラブルシューティングのクイックガイド
| 問題 | 考えられる原因 | 解決策 |
|---|---|---|
| "Cannot find module" | モックのセットアップ不足 | vi.mock() のパスが import と一致するか確認します |
| テストのタイムアウト | 非同期処理が await されていない | すべての非同期処理で await を使用していることを確認します |
| "Expected 0 calls, received 1" | モックがクリアされていない | beforeEach に vi.clearAllMocks() を追加します |
| 不安定なテスト | 状態の共有またはタイミング | セットアップを分離し、setTimeout を避けます |
| カバレッジが更新されない | キャッシュの問題 | --no-cache フラグを付けて実行します |
| "TypeError: vi.fn is not a function" | インポート不足 | 'vitest' から { vi } をインポートします |
参考ドキュメント
コメント
Markdown 対応