vitest-testing-patterns — Claude Code skill: how to install
Provides Vitest and React Testing Library conventions, mock patterns for APIs and databases, and directory rules for unit and component testing.
npx skills add curiositech/some_claude_skills --skill vitest-testing-patterns -a claude-codeWhat vitest-testing-patterns does
The skill standardizes test directories inside src/app/api/__tests__/, src/components/__tests__/, and src/lib/__tests__/. Test files use .test.ts or .test.tsx extensions. The setup relies on vitest.config.ts, a jsdom environment, src/test/setup.ts, and the v8 coverage provider.
It includes code examples for testing Next.js route handlers and isolating database calls. Component recipes pair @testing-library/react with userEvent. Clocks freeze via vi.useFakeTimers. There is also a stub pattern for @anthropic-ai/sdk.
These patterns do not cover end-to-end testing in a real browser. Use Playwright for that. Running npm test directly requires execution permissions for npm and npx in the environment.
What it helps with
- write API route tests with mocked database calls and auth sessions
- verify React component behaviors using userEvent and waitFor
- isolate third-party SDKs using vi.mock
- test helper functions with frozen system time
Example prompts for the agent
- Write a component test for FeatureComponent checking the loading spinner and submission handler
- Create unit tests for the GET endpoint in app/api/feature with a mocked auth session
- Test the formatDate utility by freezing the system clock with vi.useFakeTimers
How to install vitest-testing-patterns
1Skills CLI (Vercel)
npx skills add curiositech/some_claude_skills --skill vitest-testing-patterns -a claude-code2Manually: put the files in place
~/.claude/skills/vitest-testing-patterns/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/vitest-testing-patterns/
1Skills CLI (Vercel)
npx skills add curiositech/some_claude_skills --skill vitest-testing-patterns -a codex2Manually: put the files in place
.agents/skills/vitest-testing-patterns/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add curiositech/some_claude_skills --skill vitest-testing-patterns -a cursor2Manually: put the files in place
.cursor/skills/vitest-testing-patterns/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install curiositech/some_claude_skills vitest-testing-patterns2Manually: put the files in place
.github/skills/vitest-testing-patterns/SKILL.md1Skills CLI (Vercel)
npx skills add curiositech/some_claude_skills --skill vitest-testing-patterns -a gemini-cli2Platform CLI
gemini skills install https://github.com/curiositech/some_claude_skills --path .claude/skills/vitest-testing-patternsManually: put the files in place
.devin/skills/vitest-testing-patterns/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add curiositech/some_claude_skills --skill vitest-testing-patterns -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about vitest-testing-patterns
How to install vitest-testing-patterns in Claude Code?
Run npx skills add curiositech/some_claude_skills --skill vitest-testing-patterns -a claude-code in your terminal.
What does vitest-testing-patterns do?
It supplies patterns and conventions for writing Vitest unit tests, React Testing Library component suites, and mock setups for external dependencies.
Does vitest-testing-patterns support end-to-end testing?
No, it is scoped to unit and integration testing. E2E workflows require dedicated tools like Playwright or Cypress.
Source text
SKILL.md · MIT
---
name: vitest-testing-patterns
description: Write tests using Vitest and React Testing Library. Use when creating unit tests, component tests, integration tests, or mocking dependencies. Activates for test file creation, mock patterns,
coverage, and testing best practices.
allowed-tools: Read,Write,Edit,Bash(npm:*,npx:*)
metadata:
category: Code Quality & Testing
tags:
- testing
- code
- automation
- jest
- react
pairs-with:
- skill: test-automation-expert
reason: Vitest unit testing is one layer in a comprehensive test automation strategy
- skill: playwright-e2e-tester
reason: Unit tests (Vitest) and E2E tests (Playwright) form complementary test pyramid layers
- skill: react-performance-optimizer
reason: Component test patterns verify that performance optimizations preserve correct behavior
- skill: typescript-advanced-patterns
reason: Type-safe test utilities and mock factories leverage advanced TypeScript patterns
---
# Vitest Testing Patterns
This skill helps you write effective tests using Vitest and React Testing Library following project conventions.
## When to Use
✅ **USE this skill for:**
- Writing unit tests for utilities and functions
- Creating component tests with React Testing Library
- Setting up mocks for API calls, databases, or external services
- Integration testing patterns
- Understanding test coverage and CI setup
❌ **DO NOT use for:**
- Jest-specific patterns → similar but check Jest docs for differences
- End-to-end testing → use Playwright or Cypress skills
- Performance testing → use dedicated performance tools
- API contract testing → use OpenAPI/Pact patterns
## Test Infrastructure
**Configuration**: `vitest.config.ts`
- Environment: jsdom
- Setup file: `src/test/setup.ts`
- Coverage: v8 provider
**Commands**:
```bash
npm test # Watch mode
npm run test:run # Single run
npm run test:coverage # With coverage
```
## File Organization
```
src/
├── app/api/__tests__/ # API route tests
├── components/__tests__/ # Component tests
├── lib/__tests__/ # Library/utility tests
└── lib/{feature}/__tests__/ # Feature-specific tests
```
Name tests as `{name}.test.ts` or `{name}.test.tsx`.
## Core Testing Patterns
### 1. API Route Tests
```typescript
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { GET, POST } from '../route';
import { NextRequest } from 'next/server';
// Mock dependencies
vi.mock('@/lib/auth', () => ({
getSession: vi.fn(),
}));
vi.mock('@/db', () => ({
db: {
select: vi.fn().mockReturnThis(),
from: vi.fn().mockReturnThis(),
where: vi.fn().mockResolvedValue([]),
},
}));
describe('GET /api/feature', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('returns 401 when not authenticated', async () => {
vi.mocked(getSession).mockResolvedValue(null);
const request = new NextRequest('http://localhost/api/feature');
const response = await GET(request);
expect(response.status).toBe(401);
});
it('returns data when authenticated', async () => {
vi.mocked(getSession).mockResolvedValue({ userId: 'user-123' });
vi.mocked(db.select).mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockResolvedValue([{ id: '1', name: 'Test' }]),
}),
});
const request = new NextRequest('http://localhost/api/feature');
const response = await GET(request);
const data = await response.json();
expect(response.status).toBe(200);
expect(data).toHaveLength(1);
});
});
```
### 2. Component Tests
```typescript
import { describe, it, expect, vi } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { FeatureComponent } from '../FeatureComponent';
// Mock hooks
vi.mock('@/hooks/useAuth', () => ({
useAuth: vi.fn().mockReturnValue({
user: { id: 'user-123', name: 'Test User' },
isLoading: false,
}),
}));
describe('FeatureComponent', () => {
it('renders loading state', () => {
vi.mocked(useAuth).mockReturnValueOnce({
user: null,
isLoading: true,
});
render(<FeatureComponent />);
expect(screen.getByText(/loading/i)).toBeInTheDocument();
});
it('handles user interaction', async () => {
const user = userEvent.setup();
const onSubmit = vi.fn();
render(<FeatureComponent onSubmit={onSubmit} />);
await user.type(screen.getByRole('textbox'), 'Test input');
await user.click(screen.getByRole('button', { name: /submit/i }));
expect(onSubmit).toHaveBeenCalledWith('Test input');
});
it('displays error state', async () => {
vi.mocked(fetch).mockRejectedValueOnce(new Error('Network error'));
render(<FeatureComponent />);
await waitFor(() => {
expect(screen.getByRole('alert')).toHaveTextContent(/error/i);
});
});
});
```
### 3. Library/Utility Tests
```typescript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { processData, formatDate } from '../utils';
describe('processData', () => {
it('transforms input correctly', () => {
const input = { raw: 'data' };
const result = processData(input);
expect(result).toEqual({
processed: true,
data: 'DATA',
});
});
it('throws on invalid input', () => {
expect(() => processData(null)).toThrow('Invalid input');
});
});
describe('formatDate', () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2025-01-15T10:00:00Z'));
});
afterEach(() => {
vi.useRealTimers();
});
it('formats relative dates', () => {
const yesterday = new Date('2025-01-14T10:00:00Z');
expect(formatDate(yesterday)).toBe('yesterday');
});
});
```
## Mocking Patterns
### Module Mocking
```typescript
// Mock entire module
vi.mock('@/lib/auth', () => ({
getSession: vi.fn(),
requireAuth: vi.fn(),
}));
// Mock with partial implementation
vi.mock('date-fns', async () => {
const actual = await vi.importActual('date-fns');
return {
...actual,
format: vi.fn(() => '2025-01-15'),
};
});
// Mock default export (like Anthropic SDK)
vi.mock('@anthropic-ai/sdk', () => ({
default: class MockAnthropic {
messages = {
create: vi.fn().mockResolvedValue({
content: [{ type: 'text', text: 'Mock response' }],
usage: { input_tokens: 10, output_tokens: 20 },
}),
};
},
}));
```
### Function Mocking
```typescript
// Create mock function
const mockFn = vi.fn();
// Set return values
mockFn.mockReturnValue('sync value');
mockFn.mockResolvedValue('async value');
mockFn.mockRejectedValue(new Error('Failed'));
// One-time behavior
mockFn.mockReturnValueOnce('first call only');
// Custom implementation
mockFn.mockImplementation((arg) => arg.toUpperCase());
// Verify calls
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledTimes(2);
expect(mockFn).toHaveBeenCalledWith('expected', 'args');
```
### Chained Mock Pattern (Drizzle ORM)
```typescript
vi.mock('@/db', () => ({
db: {
select: vi.fn().mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
orderBy: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([{ id: '1' }]),
}),
}),
}),
}),
insert: vi.fn().mockReturnValue({
values: vi.fn().mockReturnValue({
returning: vi.fn().mockResolvedValue([{ id: 'new-1' }]),
}),
}),
},
}));
```
### Timer Mocking
```typescript
describe('debounced function', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('debounces calls', async () => {
const callback = vi.fn();
const debounced = debounce(callback, 300);
debounced();
debounced();
debounced();
expect(callback).not.toHaveBeenCalled();
vi.advanceTimersByTime(300);
expect(callback).toHaveBeenCalledTimes(1);
});
});
```
## Query Priorities
Use queries in this order (most to least preferred):
1. **getByRole** - Accessible queries (buttons, links, headings)
2. **getByLabelText** - Form fields with labels
3. **getByPlaceholderText** - Inputs with placeholders
4. **getByText** - Non-interactive elements
5. **getByTestId** - Last resort (data-testid)
```typescript
// Preferred
screen.getByRole('button', { name: /submit/i });
screen.getByRole('heading', { level: 1 });
screen.getByLabelText(/email/i);
// Avoid unless necessary
screen.getByTestId('submit-button');
```
## Async Patterns
```typescript
// Wait for element to appear
await waitFor(() => {
expect(screen.getByText('Loaded')).toBeInTheDocument();
});
// Find (built-in waitFor)
const element = await screen.findByText('Loaded');
// Wait for element to disappear
await waitFor(() => {
expect(screen.queryByText('Loading')).not.toBeInTheDocument();
});
```
## Test Cleanup
```typescript
import { cleanup } from '@testing-library/react';
afterEach(() => {
cleanup(); // React cleanup (automatic with setup.ts)
vi.clearAllMocks(); // Reset mock call counts
vi.resetAllMocks(); // Reset mocks to initial state
vi.restoreAllMocks(); // Restore original implementations
});
```
## Accessibility Testing
```typescript
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
it('has no accessibility violations', async () => {
const { container } = render(<Component />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
```
## Common Matchers
```typescript
// jest-dom matchers (from setup.ts)
expect(element).toBeInTheDocument();
expect(element).toBeVisible();
expect(element).toBeDisabled();
expect(element).toHaveTextContent('text');
expect(element).toHaveAttribute('href', '/path');
expect(element).toHaveClass('active');
expect(input).toHaveValue('input value');
```
## References
- [Vitest Mocking Guide](https://vitest.dev/guide/mocking)
- [React Testing Library](https://testing-library.com/docs/react-testing-library/intro)
- [Testing Library Queries](https://testing-library.com/docs/queries/about)
© curiositech · MIT · curiositech/some_claude_skills