Advanced

VitestAdvanced Techniques with Claude Code

Learn about vitestadvanced techniques using Claude Code. Practical tips and code examples included.

Vitestで高品質なテストスイートを構築する

VitestはViteベースの高速テストフレームワークです。Jest互換のAPIに加え、TypeScriptのネイティブサポート、HMR対応の監視モードなど、モダンな開発体験を提供します。Claude Codeは複雑なテストシナリオのコード生成に非常に優れています。

高度なモック戦略

Claude Codeにモジュールモックの設計を依頼しましょう。

> 外部APIクライアントのモックを設計して。
> リクエスト・レスポンスの型チェック付き、エラーケースも含めて。
import { describe, it, expect, vi, beforeEach } from "vitest";
import { UserService } from "./user-service";
import { ApiClient } from "./api-client";

// モジュールモック
vi.mock("./api-client", () => ({
  ApiClient: vi.fn().mockImplementation(() => ({
    get: vi.fn(),
    post: vi.fn(),
    put: vi.fn(),
    delete: vi.fn(),
  })),
}));

describe("UserService", () => {
  let service: UserService;
  let mockClient: ReturnType<typeof vi.mocked<ApiClient>>;

  beforeEach(() => {
    vi.clearAllMocks();
    mockClient = new ApiClient() as any;
    service = new UserService(mockClient);
  });

  it("ユーザー一覧を取得する", async () => {
    const mockUsers = [
      { id: "1", name: "Alice", email: "[email protected]" },
      { id: "2", name: "Bob", email: "[email protected]" },
    ];

    vi.mocked(mockClient.get).mockResolvedValue({ data: mockUsers });

    const result = await service.getUsers();

    expect(mockClient.get).toHaveBeenCalledWith("/users");
    expect(result).toEqual(mockUsers);
  });

  it("APIエラー時にカスタム例外をスローする", async () => {
    vi.mocked(mockClient.get).mockRejectedValue(
      new Error("Network Error")
    );

    await expect(service.getUsers()).rejects.toThrow("ユーザーの取得に失敗しました");
  });
});

パラメータテスト(test.each)

同じロジックを複数のパラメータで検証するパターンです。

describe("バリデーション関数", () => {
  it.each([
    { input: "[email protected]", expected: true },
    { input: "[email protected]", expected: true },
    { input: "invalid-email", expected: false },
    { input: "@no-local.com", expected: false },
    { input: "no-domain@", expected: false },
    { input: "", expected: false },
  ])("isValidEmail($input) => $expected", ({ input, expected }) => {
    expect(isValidEmail(input)).toBe(expected);
  });

  it.each`
    password         | minLength | hasUpper | hasNumber | expected
    ${"Abc12345"}    | ${8}      | ${true}  | ${true}   | ${true}
    ${"abc12345"}    | ${8}      | ${false} | ${true}   | ${false}
    ${"short"}       | ${8}      | ${false} | ${false}  | ${false}
    ${"NoNumbers"}   | ${8}      | ${true}  | ${false}  | ${false}
  `(
    "パスワード強度チェック: $password",
    ({ password, expected }) => {
      expect(isStrongPassword(password)).toBe(expected);
    }
  );
});

カスタムマッチャーの作成

プロジェクト固有のアサーションを定義できます。

// vitest.setup.ts
import { expect } from "vitest";

expect.extend({
  toBeWithinRange(received: number, floor: number, ceiling: number) {
    const pass = received >= floor && received <= ceiling;
    return {
      message: () =>
        `expected ${received} to be within range ${floor} - ${ceiling}`,
      pass,
    };
  },

  toBeValidDate(received: string) {
    const date = new Date(received);
    const pass = !isNaN(date.getTime());
    return {
      message: () => `expected "${received}" to be a valid date string`,
      pass,
    };
  },

  toMatchApiResponse(received: unknown) {
    const pass =
      typeof received === "object" &&
      received !== null &&
      "data" in received &&
      "meta" in received;
    return {
      message: () => `expected value to match API response shape`,
      pass,
    };
  },
});

// 型定義
declare module "vitest" {
  interface Assertion<T = any> {
    toBeWithinRange(floor: number, ceiling: number): void;
    toBeValidDate(): void;
    toMatchApiResponse(): void;
  }
}

スナップショットテストの活用

コンポーネントやデータ構造のスナップショットテストです。

import { render } from "@testing-library/react";

describe("UserProfile", () => {
  it("プロフィールカードが正しくレンダリングされる", () => {
    const { container } = render(
      <UserProfile
        user={{
          id: "1",
          name: "テストユーザー",
          email: "[email protected]",
          role: "admin",
        }}
      />
    );

    expect(container).toMatchSnapshot();
  });

  it("インラインスナップショットでAPIレスポンスを検証", () => {
    const response = transformUserResponse(rawData);

    expect(response).toMatchInlineSnapshot(`
      {
        "displayName": "テストユーザー",
        "email": "[email protected]",
        "isAdmin": true,
      }
    `);
  });
});

テストカバレッジの最適化

// vitest.config.ts
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    coverage: {
      provider: "v8",
      reporter: ["text", "json", "html", "lcov"],
      include: ["src/**/*.{ts,tsx}"],
      exclude: [
        "src/**/*.d.ts",
        "src/**/*.test.{ts,tsx}",
        "src/**/*.stories.{ts,tsx}",
        "src/types/**",
        "src/**/index.ts",
      ],
      thresholds: {
        statements: 80,
        branches: 75,
        functions: 80,
        lines: 80,
      },
    },
    setupFiles: ["./vitest.setup.ts"],
    environment: "jsdom",
    globals: true,
  },
});

非同期テストのパターン

タイマーやイベント待機など、非同期処理のテスト手法です。

describe("非同期処理のテスト", () => {
  it("デバウンス関数が正しく動作する", async () => {
    vi.useFakeTimers();

    const fn = vi.fn();
    const debounced = debounce(fn, 300);

    debounced("a");
    debounced("b");
    debounced("c");

    expect(fn).not.toHaveBeenCalled();

    vi.advanceTimersByTime(300);

    expect(fn).toHaveBeenCalledTimes(1);
    expect(fn).toHaveBeenCalledWith("c");

    vi.useRealTimers();
  });

  it("リトライロジックが正しく動作する", async () => {
    const fn = vi
      .fn()
      .mockRejectedValueOnce(new Error("1回目失敗"))
      .mockRejectedValueOnce(new Error("2回目失敗"))
      .mockResolvedValue("成功");

    const result = await retry(fn, { maxAttempts: 3, delay: 100 });

    expect(result).toBe("成功");
    expect(fn).toHaveBeenCalledTimes(3);
  });
});

Zusammenfassung

Vitestは高速な実行速度とモダンなAPIにより、テスト開発の体験を大きく向上させます。Claude Codeを活用すれば、モック設計、パラメータテスト、カスタムマッチャーなどの高度なテストパターンも素早く実装可能です。

E2Eテストの実装はPlaywright E2Eテスト実践ガイドを、APIモックの詳細はMSW APIモック活用ガイドを参照してください。Vitest公式ドキュメントも確認しておきましょう。

#Claude Code #Vitest #testing #TypeScript #quality assurance