К основному содержанию
скилл CX Codex

vitest-testing-patterns — скилл для Claude Code: как установить

Скилл обучает агента писать модульные и компонентные тесты на Vitest и React Testing Library с готовыми шаблонами моков для API и базы данных.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add curiositech/some_claude_skills --skill vitest-testing-patterns -a claude-code
Открыть на GitHub

Что делает vitest-testing-patterns

Скилл задаёт структуру каталогов тестов в проекте: папки __tests__ внутри src/app/api, src/components и src/lib. Для файлов приняты суффиксы .test.ts и .test.tsx. Конфигурация опирается на vitest.config.ts, среду jsdom, файл настройки src/test/setup.ts и провайдер v8 для расчёта покрытия.

В инструкциях собраны примеры для проверки маршрутов Next.js и изоляции вызовов к базе данных через vi.mock. Компонентные тесты используют @testing-library/react вместе с userEvent. Для тестирования функций со временем применяется vi.useFakeTimers. Есть готовый шаблон подмены внешнего SDK @anthropic-ai/sdk.

Шаблоны скилла не подходят для сквозных тестов в реальном браузере. Для них автор советует брать Playwright. Чтобы агент мог выполнять команды npm test и снимать отчёты покрытия, требуются системные права на вызов npm и npx.

Какие задачи решает

  • написать тесты маршрутов API с изоляцией базы данных и сессий
  • проверить поведение React-компонентов через userEvent и waitFor
  • настроить моки внешних SDK и библиотек через vi.mock
  • протестировать вспомогательные функции с фиксацией системного времени

Примеры запросов агенту

  • Напиши компонентный тест для FeatureComponent с проверкой состояния загрузки и клика по кнопке
  • Сделай юнит-тесты на Vitest для эндпоинта /api/feature с подменой getSession и базы данных
  • Проверь функцию formatDate, замокав системную дату через vi.useFakeTimers

Как установить vitest-testing-patterns

1Через Skills CLI (Vercel)

npx skills add curiositech/some_claude_skills --skill vitest-testing-patterns -a claude-code

2Вручную: положить файлы

~/.claude/skills/vitest-testing-patterns/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/vitest-testing-patterns/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Вопросы о vitest-testing-patterns

Как установить vitest-testing-patterns в Claude Code?

Выполните в терминале команду npx skills add curiositech/some_claude_skills --skill vitest-testing-patterns -a claude-code.

Что делает скилл vitest-testing-patterns?

Он даёт агенту правила и примеры кода для написания тестов на Vitest и React Testing Library, включая организацию папок и моки зависимостей.

Подходит ли этот скилл для сквозного E2E-тестирования?

Нет, он рассчитан на юнит- и интеграционные тесты. Для сквозных браузерных сценариев автор рекомендует использовать Playwright или Cypress.

Исходный текст

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

Похожие скиллы

Все скиллы по теме «Тесты и QA» →
improve-codebase-architecture Matt Pocock Скилл ищет архитектурные узкие места в кодовой базе и показывает варианты углубления модулей в HTML-отчёте. Подойдёт для ревью перед рефакторингом. 1 163 035установок design-taste-frontend Leonxlnx Скилл задаёт арт-дирекцию для публичных страниц и рабочих экранов, а затем проверяет результат в браузере по скриншотам. Нужен, когда интерфейс выглядит шаблонным или недоработанным. CX Codex +1 678 700установок tdd Matt Pocock Скилл управляет циклом красный-зелёный в TDD: проверяет, что тесты пишутся до кода, держит их на уровне публичных интерфейсов и не завязывает на внутреннюю структуру. 1 141 062установок skill-creator официальный anthropics Помогает создать новый скилл, доработать существующий и проверить результат через тестовые промпты и оценки. 414 180установок agent-browser vercel-labs Скилл управляет браузером через CLI: открывает страницы, заполняет формы, нажимает кнопки и проверяет веб-приложения. Подходит для задач, где агенту нужно действовать в интерфейсе. CX Codex +1 1 200 000установок prototype Matt Pocock Создаёт временный прототип, чтобы проверить логику, модель состояния или вид интерфейса до работы над основным кодом. 986 201установок

Из того же набора23

ai-video-production-master Скилл ведёт пайплайн от текста сценария до готового видео на Apple Silicon Mac: стоковые клипы, генерация T2V и I2V, облачные GPU. Пригодится, когда нужен ролик, а не отдельный кадр. CX Codex +1 398установок indie-monetization-strategist Скилл помогает инди-разработчикам выбирать модели монетизации для пет-проектов, настраивать тарифы SaaS и запускать спонсорство. Пригодится перед релизом продукта. CX Codex +1 387установок adhd-daily-planner Скилл для планирования дня с поправкой на СДВГ: реалистичные оценки времени, буферы между задачами и система «3 вещей». CX Codex +1 378установок voice-audio-engineer Скилл для работы с речью через ElevenLabs: озвучка текста, клонирование голоса, подкасты, транскрипция и голосовые интерфейсы. CX Codex +1 367установок sound-engineer Помогает агенту проектировать пространственный звук, процедурные эффекты и звук интерфейса в играх и приложениях. CX Codex +1 348установок geospatial-data-pipeline Скилл помогает обрабатывать снимки с дронов, оптимизировать GeoJSON и настраивать пространственные индексы в PostGIS. Пригодится при сборке геосервисов. CX Codex +1 345установок photo-content-recognition-curation-expert Скилл для распознавания содержимого фотографий: кластеризация лиц, поиск дублей, отбор кадров из серии и отсев скриншотов. Пригодится, когда надо разобрать большую библиотеку снимков. CX Codex +1 330установок admin-dashboard Помогает расширять админ-панель, портал разработчика и консоль операций: новые вкладки, метрики, мониторинг, управление пользователями. CX Codex +1 323установок

Спонсоры проекта

Компании, которые помогают держать аналитику открытой