用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/Leo-Atienza/atlas-claude --skill vitest-testing命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| id | SK-056 |
| name | vitest-testing |
| description | Vitest — fast unit testing with Testing Library integration and test pyramid strategy |
| keywords | vitest, testing, unit-test, component-test, testing-library, mock, coverage, tdd, test-pyramid, react-testing |
| version | 1.0.0 |
Apply when writing tests for any Vite-based or modern JS/TS project. Vitest is the 2026 default (6x faster than Jest, native ESM, shared Vite config). Test pyramid: Vitest for unit + component, Playwright for E2E. Auto-activate on keywords: vitest, test, describe, it, expect, mock, coverage.
Related skills: Playwright for E2E browser testing — via the anthropic-skills:playwright-cli skill or the Claude Browser / MCP_DOCKER browser tools (the old SK-009 Playwright skill is retired).
npm install -D vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom
// vitest.config.ts (or in vite.config.ts)
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
globals: true, // no import { describe, it, expect }
environment: 'jsdom', // 'node' for server code
setupFiles: './src/test/setup.ts',
include: ['**/*.{test,spec}.{ts,tsx}'],
coverage: {
provider: 'v8', // or 'istanbul'
reporter: ['text', 'html', 'lcov'],
thresholds: { lines: 80, branches: 80, functions: 80 },
},
css: true, // process CSS imports
},
});
Setup file:
// src/test/setup.ts
import '@testing-library/jest-dom/vitest';
import { describe, it, expect, beforeEach } from 'vitest';
describe('calculateTotal', () => {
it('returns sum of item prices', () => {
// Arrange
const items = [{ price: 10 }, { price: 20 }, { price: 30 }];
// Act
const total = calculateTotal(items);
// Assert
expect(total).toBe(60);
});
it('returns 0 for empty array', () => {
expect(calculateTotal([])).toBe(0);
});
it('throws on negative prices', () => {
expect(() => calculateTotal([{ price: -5 }])).toThrow('Invalid price');
});
});
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, vi } from 'vitest';
import { Counter } from './Counter';
describe('Counter', () => {
it('increments count on button click', async () => {
const user = userEvent.setup();
render(<Counter initialCount={0} />);
expect(screen.getByText('Count: 0')).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: /increment/i }));
expect(screen.getByText('Count: 1')).toBeInTheDocument();
});
it('calls onChange callback with new value', async () => {
const onChange = vi.fn();
const user = userEvent.setup();
render();
user.(screen.(, { : }));
(onChange).();
});
});
Module mocks:
import { vi, describe, it, expect } from 'vitest';
// Mock entire module
vi.mock('./api', () => ({
fetchUser: vi.fn(),
}));
import { fetchUser } from './api';
describe('UserService', () => {
it('fetches and transforms user', async () => {
vi.mocked(fetchUser).mockResolvedValue({ id: '1', name: 'Alice' });
const result = await getUser('1');
expect(result.displayName).toBe('Alice');
});
});
Spy on methods:
const spy = vi.spyOn(console, 'error').mockImplementation(() => {});
// ... test code that should log error ...
expect(spy).toHaveBeenCalledWith(expect.stringContaining('failed'));
spy.mockRestore();
Mock timers:
import { vi, beforeEach, afterEach } from 'vitest';
beforeEach(() => { vi.useFakeTimers(); });
afterEach(() => { vi.useRealTimers(); });
it('debounces input', async () => {
const callback = vi.fn();
const debounced = debounce(callback, 300);
debounced('hello');
expect(callback).not.toHaveBeenCalled();
vi.advanceTimersByTime(300);
expect(callback).toHaveBeenCalledWith('hello');
});
Mock fetch:
import { vi } from 'vitest';
const mockFetch = vi.fn();
global.fetch = mockFetch;
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ id: '1', name: 'Alice' }),
});
it('resolves with user data', async () => {
const user = await fetchUser('1');
expect(user).toEqual({ id: '1', name: 'Alice' });
});
it('rejects on not found', async () => {
await expect(fetchUser('999')).rejects.toThrow('Not found');
});
// Wait for condition
import { waitFor } from '@testing-library/react';
await waitFor(() => {
expect(screen.getByText('Loaded')).toBeInTheDocument();
});
import { renderHook, act } from '@testing-library/react';
it('increments counter', () => {
const { result } = renderHook(() => useCounter(0));
expect(result.current.count).toBe(0);
act(() => result.current.increment());
expect(result.current.count).toBe(1);
});
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
function createWrapper() {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
return ({ children }: { children: React.ReactNode }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
}
it('displays user data', async () => {
mockFetch.mockResolvedValueOnce({ ok: true, json: () => ({ name: 'Alice' }) });
render(<UserProfile userId="1" />, { wrapper: createWrapper() });
expect(await screen.findByText('Alice')).toBeInTheDocument();
});
it('matches snapshot', () => {
const { container } = render(<Button variant="primary">Click</Button>);
expect(container.firstChild).toMatchSnapshot();
});
// Inline snapshot (preferred — visible in test file)
it('serializes config', () => {
expect(getConfig()).toMatchInlineSnapshot(`
{
"debug": false,
"port": 3000,
}
`);
});
vitest run --coverage # run with coverage
vitest --coverage --watch # watch mode with coverage
/ E2E \ Playwright — critical user flows (5-10 tests)
/----------\
/ Component \ Vitest + Testing Library — UI behavior (many)
/--------------\
/ Unit Tests \ Vitest — pure functions, utils, hooks (most)
/--------------------\
Rules:
foo.ts → foo.test.ts in same directory.vitest # watch mode (default)
vitest run # single run (CI)
vitest run src/utils # run tests in specific directory
vitest -t "calculates" # run tests matching name
vitest --reporter=verbose # detailed output
vitest bench # run benchmarks
--pool=threads (default) for speed, --pool=forks for isolation--sequence.concurrent to run tests within a file concurrentlyvi.mock is hoisted automatically — no need for manual hoistingbeforeAll database seeding — use per-test factories instead