test-generator
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTest Generator
测试生成器
Before generating any output, read and adapt all patterns, imports, and code examples to the user's configured stack.
config/defaults.md在生成任何输出之前,请阅读,并根据用户的配置栈调整所有模式、导入语句和代码示例。
config/defaults.mdProcess
流程
- Read the source file to understand its exports, dependencies, and behavior.
- Identify the source type: API route, utility function, React component, or hook.
- Generate test cases covering: happy path, edge cases, error handling, boundary values.
- Write the test file using Vitest syntax (fall back to Jest if the project uses it).
- Include proper mocking, setup/teardown, and descriptive test names.
- 读取源文件以理解其导出内容、依赖项和行为。
- 识别源文件类型:API路由、工具函数、React组件或Hook。
- 生成涵盖以下场景的测试用例:正常路径、边缘情况、错误处理、边界值。
- 使用Vitest语法编写测试文件(如果项目使用Jest则回退到Jest语法)。
- 包含适当的模拟、初始化/清理操作以及描述性的测试名称。
File Naming and Placement
文件命名与存放规则
- Place test file adjacent to source: →
foo.ts,foo.test.ts→Foo.tsx.Foo.test.tsx - If the project uses a directory convention, follow that instead.
__tests__/ - For API route tests: →
app/api/users/route.ts.app/api/users/route.test.ts
- 将测试文件与源文件放在同一目录下:→
foo.ts,foo.test.ts→Foo.tsx。Foo.test.tsx - 如果项目使用目录约定,则遵循该约定。
__tests__/ - 对于API路由测试:→
app/api/users/route.ts。app/api/users/route.test.ts
Testing API Routes
API路由测试
Analyze the route handler and generate tests for each HTTP method.
typescript
// Source: app/api/users/route.ts (POST handler)
// Test: app/api/users/route.test.ts
import { describe, it, expect, vi, beforeEach } from "vitest";
import { POST } from "./route";
import { prisma } from "@/lib/prisma";
vi.mock("@/lib/prisma", () => ({
prisma: {
user: {
create: vi.fn(),
findUnique: vi.fn(),
},
},
}));
describe("POST /api/users", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("should create a user and return 201", async () => {
const mockUser = { id: "1", email: "test@example.com", name: "Test" };
vi.mocked(prisma.user.create).mockResolvedValue(mockUser);
const request = new Request("http://localhost/api/users", {
method: "POST",
body: JSON.stringify({ email: "test@example.com", name: "Test" }),
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(201);
expect(data).toEqual(mockUser);
});
it("should return 400 when email is missing", async () => {
const request = new Request("http://localhost/api/users", {
method: "POST",
body: JSON.stringify({ name: "Test" }),
});
const response = await POST(request);
expect(response.status).toBe(400);
});
it("should return 409 when email already exists", async () => {
vi.mocked(prisma.user.create).mockRejectedValue(
new Error("Unique constraint failed on the fields: (`email`)")
);
const request = new Request("http://localhost/api/users", {
method: "POST",
body: JSON.stringify({ email: "taken@example.com", name: "Test" }),
});
const response = await POST(request);
expect(response.status).toBe(409);
});
it("should return 500 on unexpected error", async () => {
vi.mocked(prisma.user.create).mockRejectedValue(new Error("DB down"));
const request = new Request("http://localhost/api/users", {
method: "POST",
body: JSON.stringify({ email: "test@example.com", name: "Test" }),
});
const response = await POST(request);
expect(response.status).toBe(500);
});
});分析路由处理器,为每个HTTP方法生成测试用例。
typescript
// Source: app/api/users/route.ts (POST handler)
// Test: app/api/users/route.test.ts
import { describe, it, expect, vi, beforeEach } from "vitest";
import { POST } from "./route";
import { prisma } from "@/lib/prisma";
vi.mock("@/lib/prisma", () => ({
prisma: {
user: {
create: vi.fn(),
findUnique: vi.fn(),
},
},
}));
describe("POST /api/users", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("should create a user and return 201", async () => {
const mockUser = { id: "1", email: "test@example.com", name: "Test" };
vi.mocked(prisma.user.create).mockResolvedValue(mockUser);
const request = new Request("http://localhost/api/users", {
method: "POST",
body: JSON.stringify({ email: "test@example.com", name: "Test" }),
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(201);
expect(data).toEqual(mockUser);
});
it("should return 400 when email is missing", async () => {
const request = new Request("http://localhost/api/users", {
method: "POST",
body: JSON.stringify({ name: "Test" }),
});
const response = await POST(request);
expect(response.status).toBe(400);
});
it("should return 409 when email already exists", async () => {
vi.mocked(prisma.user.create).mockRejectedValue(
new Error("Unique constraint failed on the fields: (`email`)")
);
const request = new Request("http://localhost/api/users", {
method: "POST",
body: JSON.stringify({ email: "taken@example.com", name: "Test" }),
});
const response = await POST(request);
expect(response.status).toBe(409);
});
it("should return 500 on unexpected error", async () => {
vi.mocked(prisma.user.create).mockRejectedValue(new Error("DB down"));
const request = new Request("http://localhost/api/users", {
method: "POST",
body: JSON.stringify({ email: "test@example.com", name: "Test" }),
});
const response = await POST(request);
expect(response.status).toBe(500);
});
});API Route Test Checklist
API路由测试检查清单
- Each HTTP method has at least one happy path test
- Invalid request body returns 400
- Missing/invalid auth returns 401
- Forbidden access returns 403
- Resource not found returns 404
- Duplicate/conflict returns 409
- Unexpected errors return 500 (not leak stack traces)
- 每个HTTP方法至少有一个正常路径测试
- 无效请求体返回400
- 缺失/无效的认证信息返回401
- 禁止访问返回403
- 资源不存在返回404
- 重复/冲突返回409
- 意外错误返回500(不泄露堆栈跟踪)
Testing Utility Functions
工具函数测试
Focus on pure logic, null/undefined handling, and thrown errors.
typescript
// Source: lib/utils/slugify.ts
// Test: lib/utils/slugify.test.ts
import { describe, it, expect } from "vitest";
import { slugify } from "./slugify";
describe("slugify", () => {
it("should convert a simple string to slug", () => {
expect(slugify("Hello World")).toBe("hello-world");
});
it("should handle special characters", () => {
expect(slugify("Héllo & Wörld!")).toBe("hello-world");
});
it("should collapse multiple hyphens", () => {
expect(slugify("foo---bar")).toBe("foo-bar");
});
it("should trim leading and trailing hyphens", () => {
expect(slugify("-hello-")).toBe("hello");
});
it("should return empty string for empty input", () => {
expect(slugify("")).toBe("");
});
it("should handle null/undefined gracefully", () => {
expect(slugify(null as unknown as string)).toBe("");
});
});聚焦纯逻辑、null/undefined处理以及抛出的错误。
typescript
// Source: lib/utils/slugify.ts
// Test: lib/utils/slugify.test.ts
import { describe, it, expect } from "vitest";
import { slugify } from "./slugify";
describe("slugify", () => {
it("should convert a simple string to slug", () => {
expect(slugify("Hello World")).toBe("hello-world");
});
it("should handle special characters", () => {
expect(slugify("Héllo & Wörld!")).toBe("hello-world");
});
it("should collapse multiple hyphens", () => {
expect(slugify("foo---bar")).toBe("foo-bar");
});
it("should trim leading and trailing hyphens", () => {
expect(slugify("-hello-")).toBe("hello");
});
it("should return empty string for empty input", () => {
expect(slugify("")).toBe("");
});
it("should handle null/undefined gracefully", () => {
expect(slugify(null as unknown as string)).toBe("");
});
});Testing React Components
React组件测试
Use React Testing Library. Prefer over .
getByRolegetByTestIdtsx
// Test: components/LoginForm.test.tsx
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { LoginForm } from "./LoginForm";
describe("LoginForm", () => {
it("should render email and password fields", () => {
render(<LoginForm onSubmit={vi.fn()} />);
expect(screen.getByRole("textbox", { name: /email/i })).toBeInTheDocument();
expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
});
it("should call onSubmit with form values", async () => {
const user = userEvent.setup();
const onSubmit = vi.fn();
render(<LoginForm onSubmit={onSubmit} />);
await user.type(screen.getByRole("textbox", { name: /email/i }), "a@b.com");
await user.type(screen.getByLabelText(/password/i), "secret123");
await user.click(screen.getByRole("button", { name: /sign in/i }));
expect(onSubmit).toHaveBeenCalledWith({
email: "a@b.com",
password: "secret123",
});
});
it("should show validation error for invalid email", async () => {
const user = userEvent.setup();
render(<LoginForm onSubmit={vi.fn()} />);
await user.type(screen.getByRole("textbox", { name: /email/i }), "bad");
await user.click(screen.getByRole("button", { name: /sign in/i }));
expect(screen.getByRole("alert")).toHaveTextContent(/valid email/i);
});
it("should disable submit button while loading", () => {
render(<LoginForm onSubmit={vi.fn()} isLoading />);
expect(screen.getByRole("button", { name: /sign in/i })).toBeDisabled();
});
});使用React Testing Library。优先使用而非。
getByRolegetByTestIdtsx
// Test: components/LoginForm.test.tsx
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { LoginForm } from "./LoginForm";
describe("LoginForm", () => {
it("should render email and password fields", () => {
render(<LoginForm onSubmit={vi.fn()} />);
expect(screen.getByRole("textbox", { name: /email/i })).toBeInTheDocument();
expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
});
it("should call onSubmit with form values", async () => {
const user = userEvent.setup();
const onSubmit = vi.fn();
render(<LoginForm onSubmit={onSubmit} />);
await user.type(screen.getByRole("textbox", { name: /email/i }), "a@b.com");
await user.type(screen.getByLabelText(/password/i), "secret123");
await user.click(screen.getByRole("button", { name: /sign in/i }));
expect(onSubmit).toHaveBeenCalledWith({
email: "a@b.com",
password: "secret123",
});
});
it("should show validation error for invalid email", async () => {
const user = userEvent.setup();
render(<LoginForm onSubmit={vi.fn()} />);
await user.type(screen.getByRole("textbox", { name: /email/i }), "bad");
await user.click(screen.getByRole("button", { name: /sign in/i }));
expect(screen.getByRole("alert")).toHaveTextContent(/valid email/i);
});
it("should disable submit button while loading", () => {
render(<LoginForm onSubmit={vi.fn()} isLoading />);
expect(screen.getByRole("button", { name: /sign in/i })).toBeDisabled();
});
});Component Test Checklist
组件测试检查清单
- Renders without crashing
- Displays correct content based on props
- Interactive elements respond to user events
- Conditional rendering works for all states (loading, error, empty, success)
- Accessible: interactive elements have roles and labels
- Form submission calls handler with correct values
- Error states display properly
- 渲染无崩溃
- 根据props显示正确内容
- 交互元素响应用户事件
- 条件渲染在所有状态下正常工作(加载、错误、空数据、成功)
- 可访问性:交互元素具备角色和标签
- 表单提交时调用处理器并传入正确值
- 错误状态正确显示
Testing Hooks
Hook测试
Wrap custom hooks with from React Testing Library.
renderHooktypescript
// Test: hooks/useDebounce.test.ts
import { describe, it, expect, vi } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { useDebounce } from "./useDebounce";
describe("useDebounce", () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it("should return initial value immediately", () => {
const { result } = renderHook(() => useDebounce("hello", 500));
expect(result.current).toBe("hello");
});
it("should debounce value updates", () => {
const { result, rerender } = renderHook(
({ value }) => useDebounce(value, 500),
{ initialProps: { value: "hello" } }
);
rerender({ value: "world" });
expect(result.current).toBe("hello"); // not yet updated
act(() => { vi.advanceTimersByTime(500); });
expect(result.current).toBe("world"); // now updated
});
it("should cancel previous timer on rapid updates", () => {
const { result, rerender } = renderHook(
({ value }) => useDebounce(value, 500),
{ initialProps: { value: "a" } }
);
rerender({ value: "b" });
act(() => { vi.advanceTimersByTime(300); });
rerender({ value: "c" });
act(() => { vi.advanceTimersByTime(500); });
expect(result.current).toBe("c"); // skipped "b"
});
});使用React Testing Library的包裹自定义Hook。
renderHooktypescript
// Test: hooks/useDebounce.test.ts
import { describe, it, expect, vi } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { useDebounce } from "./useDebounce";
describe("useDebounce", () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it("should return initial value immediately", () => {
const { result } = renderHook(() => useDebounce("hello", 500));
expect(result.current).toBe("hello");
});
it("should debounce value updates", () => {
const { result, rerender } = renderHook(
({ value }) => useDebounce(value, 500),
{ initialProps: { value: "hello" } }
);
rerender({ value: "world" });
expect(result.current).toBe("hello"); // not yet updated
act(() => { vi.advanceTimersByTime(500); });
expect(result.current).toBe("world"); // now updated
});
it("should cancel previous timer on rapid updates", () => {
const { result, rerender } = renderHook(
({ value }) => useDebounce(value, 500),
{ initialProps: { value: "a" } }
);
rerender({ value: "b" });
act(() => { vi.advanceTimersByTime(300); });
rerender({ value: "c" });
act(() => { vi.advanceTimersByTime(500); });
expect(result.current).toBe("c"); // skipped "b"
});
});Mocking Patterns
模拟模式
Mock a module
模拟模块
typescript
vi.mock("@/lib/prisma", () => ({
prisma: { user: { findMany: vi.fn() } },
}));typescript
vi.mock("@/lib/prisma", () => ({
prisma: { user: { findMany: vi.fn() } },
}));Mock next/navigation
模拟next/navigation
typescript
vi.mock("next/navigation", () => ({
useRouter: () => ({ push: vi.fn(), back: vi.fn() }),
useSearchParams: () => new URLSearchParams("q=test"),
usePathname: () => "/dashboard",
}));typescript
vi.mock("next/navigation", () => ({
useRouter: () => ({ push: vi.fn(), back: vi.fn() }),
useSearchParams: () => new URLSearchParams("q=test"),
usePathname: () => "/dashboard",
}));Mock fetch
模拟fetch
typescript
const mockFetch = vi.fn();
global.fetch = mockFetch;
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ data: "test" }),
});typescript
const mockFetch = vi.fn();
global.fetch = mockFetch;
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ data: "test" }),
});Test Naming Convention
测试命名规范
Use descriptive names following the pattern: .
should [expected behavior] when [condition]should return 401 when auth token is missingshould render loading spinner when data is fetchingshould throw TypeError when input is not a string
使用描述性名称,遵循模式:。
should [预期行为] when [条件]should return 401 when auth token is missingshould render loading spinner when data is fetchingshould throw TypeError when input is not a string
Output Format
输出格式
undefinedundefinedGenerated Tests
生成的测试用例
Source:
Test file:
Framework: Vitest
path/to/source.tspath/to/source.test.ts源文件:
测试文件:
测试框架: Vitest
path/to/source.tspath/to/source.test.tsTest Cases
测试用例列表
| # | Test | Category |
|---|---|---|
| 1 | should ... | Happy path |
| 2 | should ... | Edge case |
| 3 | should ... | Error handling |
[Generated code block]
| # | 测试内容 | 分类 |
|---|---|---|
| 1 | should ... | 正常路径 |
| 2 | should ... | 边缘情况 |
| 3 | should ... | 错误处理 |
[生成的代码块]
Coverage Notes
覆盖率说明
- [Any areas that need additional manual test cases]
undefined- [任何需要额外手动测试用例的区域]
undefinedVerification Loop
验证循环
After generating tests, run them with the project's test runner. If any test fails due to a generation error (not a legitimate bug), analyze the failure, fix the test, and re-run. Repeat up to 3 times. If a test fails because it caught a real bug in the source code, flag it clearly in the output: . Only deliver tests that either pass or explicitly flag real bugs.
REAL BUG FOUND: [description]生成测试后,使用项目的测试运行器执行测试。如果任何测试因生成错误(而非合法bug)失败,请分析失败原因、修复测试并重新运行。重复此步骤最多3次。如果测试失败是因为捕获到源文件中的真实bug,请在输出中明确标记:。仅交付可通过或明确标记真实bug的测试用例。
发现真实BUG: [描述]Reference
参考
See references/testing-patterns.md for Vitest setup, Prisma mocking, and MSW patterns.
请查看references/testing-patterns.md获取Vitest配置、Prisma模拟及MSW模式相关内容。