一键导入
testing
Write tests with Vitest and Testing Library. Use when creating tests, writing specs, mocking, or setting up test coverage.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Write tests with Vitest and Testing Library. Use when creating tests, writing specs, mocking, or setting up test coverage.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Git workflow for branches, commits, and PRs. Use for commit, branch, pr, pull request, conventional, push, feat, fix, chore, merge, rebase
Storybook stories and interaction tests. Use for story, stories, storybook, chromatic, visual test, interaction test, play function, component test
TypeScript and unicorn linting patterns. Use for typescript, array, for-of, reduce, forEach, throw, catch, modern js, es modules, string, number, error handling
Zod v4 schema validation. Use for zod, schema, validation, parse, safeParse, infer, coerce, transform, refine, z.object, z.string, z.email, z.url
Better Auth authentication. Use for auth, login, logout, session, user, signup, register, protect, middleware, password, oauth, social
Drizzle ORM + PostgreSQL database layer. Use for db, database, query, schema, table, migrate, sql, postgres, drizzle, model, relation
| name | testing |
| description | Write tests with Vitest and Testing Library. Use when creating tests, writing specs, mocking, or setting up test coverage. |
pnpm test # Run all
pnpm test:watch # Watch mode
pnpm test:coverage # With coverage
import { render, screen } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import { Button } from '@oakoss/ui';
describe('Button', () => {
it('renders with text', () => {
render(<Button>Click me</Button>);
expect(
screen.getByRole('button', { name: /click me/i }),
).toBeInTheDocument();
});
});
import userEvent from '@testing-library/user-event';
it('handles click', async () => {
const user = userEvent.setup();
const onClick = vi.fn();
render(<Button onPress={onClick}>Click</Button>);
await user.click(screen.getByRole('button'));
expect(onClick).toHaveBeenCalled();
});
getByRole - Accessible elementsgetByLabelText - Form inputsgetByText - Text contentgetByTestId - Last resortconst mockFn = vi.fn().mockReturnValue('result');
vi.mock('@oakoss/ui', () => ({
Button: vi.fn(({ children }) => <button>{children}</button>),
}));
const spy = vi.spyOn(utils, 'cn');
await screen.findByText('Loaded');
await waitFor(() => {
expect(screen.getByRole('list')).toHaveTextContent('Item');
});
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
function createTestQueryClient() {
return new QueryClient({
defaultOptions: {
queries: { retry: false, gcTime: 0 },
mutations: { retry: false },
},
});
}
function renderWithQuery(ui: React.ReactElement) {
const queryClient = createTestQueryClient();
return render(
<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>,
);
}
it('loads and displays data', async () => {
vi.mock('@/lib/api', () => ({
getPosts: vi.fn().mockResolvedValue([{ id: '1', title: 'Test' }]),
}));
renderWithQuery(<PostList />);
expect(await screen.findByText('Test')).toBeInTheDocument();
});
import userEvent from '@testing-library/user-event';
it('validates and submits form', async () => {
const user = userEvent.setup();
const onSubmit = vi.fn();
render(<LoginForm onSubmit={onSubmit} />);
await user.type(screen.getByLabelText(/email/i), 'test@example.com');
await user.type(screen.getByLabelText(/password/i), 'password123');
await user.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123',
});
});
});
it('shows validation errors', async () => {
const user = userEvent.setup();
render(<LoginForm />);
await user.click(screen.getByRole('button', { name: /submit/i }));
expect(await screen.findByText(/email is required/i)).toBeInTheDocument();
});
import { RouterProvider, createMemoryHistory } from '@tanstack/react-router';
function renderWithRouter(ui: React.ReactElement, { route = '/' } = {}) {
const history = createMemoryHistory({ initialEntries: [route] });
const router = createRouter({ routeTree, history });
return render(<RouterProvider router={router}>{ui}</RouterProvider>);
}
it('navigates on click', async () => {
const user = userEvent.setup();
renderWithRouter(<Navigation />, { route: '/' });
await user.click(screen.getByRole('link', { name: /about/i }));
expect(window.location.pathname).toBe('/about');
});
vi.mock('@/lib/server-functions', () => ({
getUser: vi.fn(),
updateUser: vi.fn(),
}));
import { getUser, updateUser } from '@/lib/server-functions';
beforeEach(() => {
vi.mocked(getUser).mockResolvedValue({ id: '1', name: 'Test User' });
});
it('loads user data', async () => {
render(<UserProfile userId="1" />);
expect(await screen.findByText('Test User')).toBeInTheDocument();
expect(getUser).toHaveBeenCalledWith({ data: { id: '1' } });
});
| Mistake | Correct Pattern |
|---|---|
Using getBy for async content | Use findBy or waitFor for async |
| Testing implementation details | Test behavior, not internal state |
Using getByTestId first | Prefer getByRole, getByLabelText |
Missing userEvent.setup() | Always call userEvent.setup() first |
| Not wrapping in act | Use userEvent which handles this |
| Mocking too much | Only mock external dependencies |
| Not cleaning up mocks | Use vi.clearAllMocks() in beforeEach |
| Shared QueryClient in tests | Create fresh QueryClient per test |
| Retry enabled in test queries | Set retry: false in test QueryClient |
| Testing third-party components | Trust the library, test your usage |
Explore agentTask agentcode-reviewer agent| Task | Skill |
|---|---|
| Query testing patterns | tanstack-query |
| Form testing patterns | tanstack-form |
| Router testing patterns | tanstack-router |
| Error boundary testing | error-boundaries |
| Integration flows | integration-patterns |