Agent skill
writing-tests
Write user-centric tests following Kent C. Dodds principles. Use when asked to write tests for components, hooks, or features. Generates tests that test behavior, use accessibility queries, and mock only at boundaries.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/writing-tests-planetaryescape-blah-chat
SKILL.md
Writing Tests Skill
Write tests that give confidence the app works for users, not that code is structured a certain way.
Core Philosophy
"The more your tests resemble the way your software is used, the more confidence they can give you." - Kent C. Dodds
- Test behavior, not implementation - Test what users see and do
- Use accessibility queries -
getByRole,getByLabelTextovergetByTestId - Mock at boundaries only - Network/Convex hooks, not components
- Integration tests > unit tests - Test real workflows
Query Priority
| Priority | Query | When |
|---|---|---|
| 1 | getByRole |
Semantic HTML elements |
| 2 | getByLabelText |
Form fields |
| 3 | getByText |
Buttons, links |
| 4 | getByTestId |
LAST RESORT |
Existing Infrastructure (USE THESE)
DO NOT create new test utilities. Use:
// Factories - src/lib/test/factories.ts
import { createOptimisticMessage, createTestMessageData, createTestUserData, createTestConversationData, createMockIdentity } from "@/lib/test/factories";
// API helpers - src/lib/test/api-helpers.ts
import { createMockRequest, assertEnvelopeSuccess, assertEnvelopeError, unwrapData } from "@/lib/test/api-helpers";
Test Patterns
Component Tests
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi, beforeEach } from "vitest";
// Mock BEFORE importing component
vi.mock("convex/react", () => ({
useQuery: vi.fn(() => null),
useMutation: vi.fn(() => vi.fn()),
}));
const mockMutation = vi.fn();
vi.mock("@/lib/hooks/mutations", () => ({
useSendMessage: () => ({ mutate: mockMutation, isPending: false }),
}));
// Import AFTER mocks
import { MyComponent } from "../MyComponent";
describe("MyComponent", () => {
beforeEach(() => vi.clearAllMocks());
it("sends data when user submits", async () => {
const user = userEvent.setup();
render(<MyComponent {...props} />);
// Use accessibility queries
const input = screen.getByLabelText("Message input");
await user.type(input, "Hello{Enter}");
expect(mockMutation).toHaveBeenCalledWith(
expect.objectContaining({ content: "Hello" })
);
});
});
Convex Tests
import { convexTest } from "convex-test";
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import { api } from "../_generated/api";
import schema from "../schema";
import { createMockIdentity, createTestUserData, createTestConversationData } from "@/lib/test/factories";
describe("conversations", () => {
// Use fake timers if testing scheduled functions
beforeEach(() => vi.useFakeTimers());
afterEach(() => vi.useRealTimers());
it("returns only user's conversations", async () => {
const t = convexTest(schema);
const identity = createMockIdentity();
await t.run(async (ctx) => {
const userId = await ctx.db.insert("users", createTestUserData({
clerkId: identity.subject,
}));
await ctx.db.insert("conversations", createTestConversationData(userId));
});
const asUser = t.withIdentity(identity);
// @ts-ignore - Type depth exceeded with 94+ Convex modules
const result = await asUser.query(api.conversations.list, {});
expect(result).toHaveLength(1);
});
});
API Route Tests
// Mocks MUST be BEFORE imports
vi.mock("@/lib/api/dal/conversations", () => ({
conversationsDAL: { list: vi.fn(), create: vi.fn() },
}));
import { conversationsDAL } from "@/lib/api/dal/conversations";
import { createMockRequest, assertEnvelopeSuccess } from "@/lib/test/api-helpers";
describe("/api/v1/conversations", () => {
beforeEach(() => vi.clearAllMocks());
it("returns list with envelope", async () => {
vi.mocked(conversationsDAL.list).mockResolvedValue([]);
// Dynamic import AFTER mock setup
const { GET } = await import("../route");
const response = await GET(createMockRequest("/api/v1/conversations"));
const json = await response.json();
assertEnvelopeSuccess(json);
});
});
What to Mock
Mock:
convex/reacthooks (useQuery, useMutation)- Custom mutation hooks (
@/lib/hooks/mutations) - DAL modules (
@/lib/api/dal/*) - Browser APIs (localStorage, clipboard)
- System clock (
vi.useFakeTimers())
Don't Mock:
- Child components
- Domain objects
- React itself
- CSS/styling
Anti-Patterns to Avoid
// BAD: Over-mocking
vi.mock("./ChildA");
vi.mock("./ChildB");
vi.mock("../hooks/useX");
// BAD: Testing implementation
expect(component.state.loading).toBe(true);
// BAD: Snapshot for behavior
expect(component).toMatchSnapshot();
// BAD: Test IDs when a11y query works
screen.getByTestId("submit-button");
File Locations
| Test Type | Location |
|---|---|
| Component | src/components/[name]/__tests__/[Name].test.tsx |
| Convex | convex/__tests__/[name].test.ts |
| API Route | src/app/api/v1/__tests__/[name].test.ts |
| Utility | src/lib/[path]/__tests__/[name].test.ts |
| E2E | e2e/[name].spec.ts |
Reference
Full philosophy: docs/testing/testing-philosophy.md
Phase docs: docs/testing/phase-*.md
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?