Back to list
tqer39

unit-test

by tqer39

Personal blog monorepo powered by Next.js, Hono, and Cloudflare.

0🍴 0📅 Jan 25, 2026

SKILL.md


name: unit-test description: 単体テストの作成。「テストを書きたい」「ユニットテストを追加」「関数をテスト」などのリクエスト時に使用。

Unit Test

コンポーネント・関数の単体テストを作成するスキル。

推奨テストフレームワーク

  • Vitest: 高速なテストランナー(Vite ベース)
  • React Testing Library: コンポーネントテスト
  • @testing-library/user-event: ユーザー操作シミュレーション

セットアップ(未設定の場合)

pnpm add -D vitest @vitejs/plugin-react jsdom
pnpm add -D @testing-library/react @testing-library/jest-dom @testing-library/user-event

vitest.config.ts

import react from "@vitejs/plugin-react";
import { defineConfig } from "vitest/config";

export default defineConfig({
  plugins: [react()],
  test: {
    environment: "jsdom",
    globals: true,
    setupFiles: ["./vitest.setup.ts"],
    include: ["src/**/*.{test,spec}.{ts,tsx}"],
  },
  resolve: {
    alias: {
      "@": "./src",
    },
  },
});

vitest.setup.ts

import "@testing-library/jest-dom/vitest";

テストファイル配置

src/
├── components/
│   ├── Button.tsx
│   └── Button.test.tsx      # コンポーネントと同階層
├── lib/
│   ├── utils.ts
│   └── utils.test.ts        # ユーティリティと同階層
└── __tests__/               # 統合テスト用(オプション)

テストテンプレート

コンポーネントテスト

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";

import { Button } from "./Button";

describe("Button", () => {
  it("renders with text", () => {
    render(<Button>Click me</Button>);
    expect(screen.getByRole("button", { name: /click me/i })).toBeInTheDocument();
  });

  it("calls onClick when clicked", async () => {
    const user = userEvent.setup();
    const handleClick = vi.fn();

    render(<Button onClick={handleClick}>Click me</Button>);
    await user.click(screen.getByRole("button"));

    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  it("is disabled when disabled prop is true", () => {
    render(<Button disabled>Click me</Button>);
    expect(screen.getByRole("button")).toBeDisabled();
  });
});

関数テスト

import { describe, expect, it } from "vitest";

import { formatDate, slugify } from "./utils";

describe("formatDate", () => {
  it("formats date correctly", () => {
    expect(formatDate("2025-01-01")).toBe("2025年1月1日");
  });

  it("handles invalid date", () => {
    expect(formatDate("invalid")).toBe("Invalid Date");
  });
});

describe("slugify", () => {
  it("converts string to slug", () => {
    expect(slugify("Hello World")).toBe("hello-world");
  });
});

非同期テスト

import { render, screen, waitFor } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";

import { AsyncComponent } from "./AsyncComponent";

describe("AsyncComponent", () => {
  it("shows loading state initially", () => {
    render(<AsyncComponent />);
    expect(screen.getByText(/loading/i)).toBeInTheDocument();
  });

  it("shows data after loading", async () => {
    render(<AsyncComponent />);
    await waitFor(() => {
      expect(screen.getByText(/data loaded/i)).toBeInTheDocument();
    });
  });
});

テスト実行コマンド

# 全テスト実行
pnpm test

# ウォッチモード
pnpm test:watch

# カバレッジ付き
pnpm test:coverage

# 特定ファイル
pnpm test src/lib/utils.test.ts

package.json scripts

{
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest",
    "test:coverage": "vitest run --coverage"
  }
}

テスト作成の手順

  1. テスト対象のコード確認
  2. テストケースの洗い出し
    • 正常系
    • 異常系
    • 境界値
  3. テストファイル作成(*.test.ts(x)
  4. describe でグループ化
  5. it/test で個別ケース記述
  6. テスト実行・確認

ベストプラクティス

  • AAA パターン: Arrange(準備)→ Act(実行)→ Assert(検証)
  • 1テスト1検証: 各テストは1つの振る舞いを検証
  • 実装詳細をテストしない: 内部実装ではなく振る舞いをテスト
  • getByRole 優先: アクセシビリティを意識したクエリ
  • ユーザー視点: ユーザーの操作をシミュレート

クエリ優先順位

  1. getByRole - アクセシビリティ
  2. getByLabelText - フォーム要素
  3. getByPlaceholderText - 入力フィールド
  4. getByText - テキストコンテンツ
  5. getByTestId - 最終手段

Score

Total Score

65/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon