Advanced

VitestAdvanced Techniques: Claude Code 활용 가이드

vitestadvanced techniques: Claude Code 활용. 실용적인 팁과 코드 예시를 포함합니다.

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);
  });
});

정리

Vitestは高速な実行速度とモダンなAPIにより、테스트개발の体験を大きく向上させます。Claude Codeを활용すれば、모크설계、매개변수테스트、カスタムマッチャーなどの高度な테스트パターンも빠르게구현가능합니다。

E2E 테스트の구현はPlaywright E2E 테스트実践가이드を、API모크의 상세 정보는MSW API모크활용가이드를 참고하세요.Vitest공식 문서도 확인해 두세요.

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