| name | test-guidelines |
| description | Comprehensive testing guidelines for Vitest and React Testing Library. Covers quality standards, AAA pattern, naming conventions, branch coverage, and best practices. Reference this skill when creating or updating test code during Phase 2 (Testing & Stories). |
Test Guidelines - Vitest / React Testing Library
This document defines quality standards, structure, and naming conventions for test code using Vitest and React Testing Library.
Core Guidelines
- Explicitly import necessary functions from
vitest in all test files. Do not rely on global definitions.
- Write
describe / test descriptions in Japanese with specific conditions and expected results, using the format "when [condition], it should [result]".
- Follow the AAA (Arrange-Act-Assert) pattern strictly, comparing using
actual and expected variables. One test, one assertion (multiple properties can be compared as object).
- Prohibit nested
describe blocks. Place shared data in the top-level describe scope.
- Identify all branches and exception paths to ensure meaningful coverage. Verify behavior, not implementation details.
- Limit snapshots to verifying semantic HTML and accessibility attributes. Do not use them for style changes.
Code Examples
Basic Test Structure
import { describe, expect, test } from "vitest";
import { calculateTotal } from "./calculateTotal";
describe("calculateTotal", () => {
test("商品が1つの場合、その価格を返すこと", () => {
const items = [{ price: 100 }];
const expected = 100;
const actual = calculateTotal(items);
expect(actual).toBe(expected);
});
test("商品が複数の場合、合計金額を返すこと", () => {
const items = [{ price: 100 }, { price: 200 }, { price: 300 }];
const expected = 600;
const actual = calculateTotal(items);
expect(actual).toBe(expected);
});
test("商品が空の場合、0を返すこと", () => {
const items: Array<{ price: number }> = [];
const expected = 0;
const actual = calculateTotal(items);
expect(actual).toBe(expected);
});
});
Component Test Example
import { render, screen } from "@testing-library/react";
import { describe, expect, test, vi } from "vitest";
import { Button } from "./Button";
describe("Button", () => {
test("children が表示されること", () => {
const expected = "クリック";
render(<Button>{expected}</Button>);
const actual = screen.getByRole("button", { name: expected });
expect(actual).toBeInTheDocument();
});
test("disabled が true の場合、ボタンが無効化されること", () => {
render(<Button disabled>クリック</Button>);
const actual = screen.getByRole("button");
expect(actual).toBeDisabled();
});
test("クリック時に onClick が呼ばれること", async () => {
const { user } = render(<Button onClick={handleClick}>クリック</Button>);
const handleClick = vi.fn();
const button = screen.getByRole("button");
await user.click(button);
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
Shared Data Management
import { describe, expect, test } from "vitest";
import { formatDate } from "./formatDate";
describe("formatDate", () => {
const testDate = new Date("2024-01-15T10:30:00");
test("年月日形式でフォーマットされること", () => {
const format = "YYYY-MM-DD";
const expected = "2024-01-15";
const actual = formatDate(testDate, format);
expect(actual).toBe(expected);
});
test("時分秒を含む形式でフォーマットされること", () => {
const format = "YYYY-MM-DD HH:mm:ss";
const expected = "2024-01-15 10:30:00";
const actual = formatDate(testDate, format);
expect(actual).toBe(expected);
});
});
Testing Error Cases
import { describe, expect, test } from "vitest";
import { divide } from "./divide";
describe("divide", () => {
test("正常に除算が行われること", () => {
const a = 10;
const b = 2;
const expected = 5;
const actual = divide(a, b);
expect(actual).toBe(expected);
});
test("0で除算した場合、エラーがスローされること", () => {
const a = 10;
const b = 0;
expect(() => divide(a, b)).toThrow("Division by zero");
});
});
Bad Examples (Avoid These)
describe("UserService", () => {
describe("getUser", () => {
describe("when user exists", () => {
test("should return user", () => {
});
});
});
});
test("ユーザー情報が正しいこと", () => {
const user = getUser();
expect(user.name).toBe("Taro");
expect(user.age).toBe(30);
expect(user.email).toBe("taro@example.com");
});
test("ユーザー情報が正しいこと", () => {
const expected = {
name: "Taro",
age: 30,
email: "taro@example.com",
};
const actual = getUser();
expect(actual).toEqual(expected);
});
test("state が更新されること", () => {
const { result } = renderHook(() => useCounter());
expect(result.current.count).toBe(0);
act(() => result.current.increment());
expect(result.current.count).toBe(1);
});
test("カウンターが1増加すること", () => {
render(<Counter />);
const button = screen.getByRole("button", { name: "増やす" });
const counter = screen.getByText("0");
user.click(button);
expect(screen.getByText("1")).toBeInTheDocument();
});
test("合計金額を計算すること", () => {
expect(calculateTotal([{ price: 100 }, { price: 200 }])).toBe(300);
});
test("合計金額を計算すること", () => {
const items = [{ price: 100 }, { price: 200 }];
const expected = 300;
const actual = calculateTotal(items);
expect(actual).toBe(expected);
});
Additional Guidelines
Test File Naming
- Test files should be named
[ComponentName].test.tsx or [functionName].test.ts
- Place test files in the same directory as the component/function being tested
Import Organization
import { render, screen } from "@testing-library/react";
import { describe, expect, test, vi } from "vitest";
import { ComponentUnderTest } from "./ComponentUnderTest";
Snapshot Testing
Use snapshots only for:
- Verifying semantic HTML structure
- Checking accessibility attributes (aria-*, role, etc.)
- Ensuring critical DOM structure remains stable
Do NOT use snapshots for:
- CSS class names or inline styles
- Testing visual appearance
- Replace proper assertions