vitest について
vitest は、TypeScript の React/Next.js プロジェクトで Vitest v4 を使うためのテストスキルです。テスト作成、失敗の調査、テストスイートの保守を支援し、構成、モック、非同期テスト、カバレッジ、デバッグを扱います。
主な機能
- 01ユニットテストと非同期テストの作成
- 02関数、モジュール、タイマーのモック
- 03テスト構成と状態のリセット
- 04失敗の調査とカバレッジ確認
- 05React と monorepo テストの参考資料
活用シーン
- 01TypeScript プロジェクトへのユニットテスト追加
- 02React/Next.js コードと DOM 動作のテスト
- 03モック不具合やテスト状態の漏れの調査
- 04カバレッジ確認と失敗ケースのデバッグ
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ sablier-labs/ agent-skills --skill vitestインストール後、Claude Code で/vitestを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/monorepo-testing.mdtesting-patterns.mdtroubleshooting.md詳細
あなたの役割
あなたは、TypeScript の React/Next.js プロジェクトにおける Vitest v4 のテスト作成に精通しています。ユーザーが高品質なテストを作成し、失敗をデバッグし、テストスイートを効率よく保守できるよう支援します。
一般的なセットアップ:
- jsdom 環境での Vitest v4
- グローバルの有効化(
describe、test、expect、vi) - プロジェクトごとに設定されたパスエイリアス
クイックスタート
テストの実行
# Run all unit tests
nlx vitest run
# Run tests matching pattern
nlx vitest run tokens
# Run specific test file
nlx vitest run src/utils/format.test.ts
# Run tests with matching name
nlx vitest run -t "adds token"
# Watch mode
nlx vitest
最初のテストを書く
ファイル名: *.test.ts または *.test.tsx
配置場所: ソースファイルと同じ場所に配置
import { describe, test, expect } from "vitest";
import { myFunction } from "./my-function";
describe("myFunction", () => {
test("returns expected value", () => {
expect(myFunction(5)).toBe(10);
});
});
プロジェクト固有のパターン
テストの構成
視覚的な区切りと分かりやすいブロックを使用します。
describe("TokenStore", () => {
/* ----------------------------------------------------------------
* Setup
* ------------------------------------------------------------- */
const validToken = { address: "0x123", symbol: "TEST" };
afterEach(() => {
// Reset state between tests
useTokensStore.getState().clearAll();
});
/* ----------------------------------------------------------------
* Adding tokens
* ------------------------------------------------------------- */
describe("addToken", () => {
test("adds valid token and returns true", () => {
const success = useTokensStore.getState().addToken(validToken);
expect(success).toBe(true);
});
});
});
クリーンアップのパターン
afterEach() で必ず状態をリセットします。
import { afterEach } from "vitest";
afterEach(() => {
// Reset mocks
vi.clearAllMocks();
// Reset environment
process.env.NODE_ENV = originalEnv;
// Reset stores
});
ファクトリーモックのパターン
複雑なモックにはファクトリー関数を使用します。
// __mocks__/localStorage.ts
import { vi } from "vitest";
export function createLocalStorageMock() {
const store = new Map<string, string>();
return {
getItem: vi.fn((key: string) => store.get(key) ?? null),
setItem: vi.fn((key: string, value: string) => {
store.set(key, value);
}),
removeItem: vi.fn((key: string) => {
store.delete(key);
}),
clear: vi.fn(() => {
store.clear();
})
};
}
// Usage in tests
import { createLocalStorageMock } from "./__mocks__/localStorage";
const mockStorage = createLocalStorageMock();
global.localStorage = mockStorage as Storage;
共有セットアップファイル
グローバルモックと設定はセットアップファイル(例:tests/setup.ts)に記述します。
import { vi } from "vitest";
// Mock logger for all tests
vi.mock("@/utils/logger", () => ({
createLogger: vi.fn(() => ({
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn()
}))
}));
よくあるテストシナリオ
ユーティリティのテスト
import { describe, test, expect, afterEach } from "vitest";
import { getEnvironment } from "./environment";
describe("getEnvironment", () => {
const originalEnv = process.env.NODE_ENV;
afterEach(() => {
process.env.NODE_ENV = originalEnv;
});
test("returns production when NODE_ENV is production", () => {
process.env.NODE_ENV = "production";
expect(getEnvironment()).toBe("production");
});
test("returns development by default", () => {
process.env.NODE_ENV = undefined;
expect(getEnvironment()).toBe("development");
});
});
非同期テスト
test("async function resolves correctly", async () => {
const result = await fetchData();
expect(result).toEqual({ data: "value" });
});
test("async function rejects with error", async () => {
await expect(failingFunction()).rejects.toThrow("Error message");
});
関数のモック
import { vi } from "vitest";
// Mock a function
const mockCallback = vi.fn((x: number) => x * 2);
mockCallback(5);
expect(mockCallback).toHaveBeenCalledWith(5);
expect(mockCallback).toHaveReturnedWith(10);
// Spy on object method
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
console.log("test");
expect(spy).toHaveBeenCalledWith("test");
spy.mockRestore();
モジュールのモック
// At top level, before imports
vi.mock("./api-client", () => ({
fetchUser: vi.fn(() => Promise.resolve({ id: 1, name: "Test" }))
}));
import { fetchUser } from "./api-client";
test("uses mocked API", async () => {
const user = await fetchUser();
expect(user.name).toBe("Test");
});
タイマーのモック
import { vi } from "vitest";
test("debounced function", () => {
vi.useFakeTimers();
const callback = vi.fn();
const debounced = debounce(callback, 1000);
debounced();
debounced();
debounced();
vi.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalledTimes(1);
vi.useRealTimers();
});
テスト失敗のデバッグ
テスト出力の読み方
次の情報に注目します。
- ファイルと行番号 - 失敗が発生した場所
- 期待値と受信値 - 何が問題だったか
- スタックトレース - フレームワーク内部の情報は無視し、自分のコードに注目
よくある失敗
テスト間で状態が漏れる:
// Problem: Previous test left state
test("first test", () => {
store.addItem("test");
});
test("second test", () => {
expect(store.items).toHaveLength(0); // Fails! Still has "test"
});
// Solution: Add cleanup
afterEach(() => {
store.clear();
});
モックが動作しない:
// Problem: Mock path doesn't match import
vi.mock("./utils/logger");
import { logger } from "@/utils/logger"; // Different path!
// Solution: Match exact import path
vi.mock("@/utils/logger");
非同期処理がタイムアウトする:
// Problem: Default 5s timeout too short
test("slow operation", async () => {
await verySlowOperation(); // Times out
});
// Solution: Increase timeout
test("slow operation", async () => {
await verySlowOperation();
}, 10000); // 10 second timeout
デバッグツール
nlx vitest --reporter=verbose # Detailed output
nlx vitest --ui # Visual debugging interface
nlx vitest --coverage # See what's tested
nlx vitest --inspect # Node debugger
nlx vitest --run # Disable watch mode
ベストプラクティス
DO
- テストをソースファイルと同じ場所に配置する(
feature.ts+feature.test.ts) - 関連するテストをまとめるために
describeブロックを使用する - 状態/モックをクリーンアップする
afterEach()を追加する - 分かりやすさのために視覚的な区切りを使用する(
/* --- */) - 実装ではなく振る舞いをテストする
- モックには明示的な型注釈を使用する
- テストの対象を絞り、独立性を保つ
- バグを修正する前にテストを書く(まずバグを再現する)
DON'T
- 実装の詳細(内部変数)をテストしない
- テスト間で状態を共有しない
- すべてをモックしない(境界のみをモックする:ネットワーク、ストレージ、時間)
- モック/タイマーの復元を忘れない
- テストで
any型を使用しない - DOM 構造に依存する壊れやすいテストを作成しない
- テストユーティリティに後方互換性のためのハックを追加しない
発展的なトピック
詳しくは ./references/ ディレクトリを参照してください。
testing-patterns.md- パターンの総合ガイド(コンポーネントテスト、複雑なモック、非同期パターン)monorepo-testing.md- ワークスペース固有の方針(共有テストとアプリテスト、パスエイリアス、構成)troubleshooting.md- デバッグガイド(よくあるエラー、パフォーマンス、カバレッジ、CI/CD)
カバレッジ分析
カバレッジを追加するには:
// vitest.config.ts
export default defineConfig({
test: {
coverage: {
provider: "v8",
reporter: ["text", "html", "json"],
exclude: ["**/*.test.ts", "**/__mocks__/**", "**/node_modules/**"]
}
}
});
次のコマンドで実行します:nlx vitest --coverage
設定リファレンス
設定例:vitest.config.ts
{
environment: "jsdom", // React/DOM APIs available
globals: true, // No imports needed for describe/test/expect
include: ["**/*.test.{js,ts,tsx}"],
exclude: ["**/node_modules/**", "**/e2e/**"],
setupFiles: ["./tests/setup.ts"],
alias: {
"@": "./src",
// Add your project's path aliases
},
}
次のステップ
- コンポーネントテストについて -
./references/testing-patterns.md(React Testing Library のセットアップ)を参照 - モノレポ固有の方針について -
./references/monorepo-testing.mdを参照 - デバッグのヘルプについて -
./references/troubleshooting.mdを参照
シンプルなユニットテストから始め、必要に応じてコンポーネントテストを追加します。
コメント
Markdown 対応