搜索

PythonReactSEOPDF
GitHub Skill

react-testing

Comprehensive testing strategies with Vitest, React Testing Library, and Jest. Use when writing tests for React components, hooks, and integration testing.

majesticlabs-dev☆ 20 仓库 Stars3 仓库 Forks

安装与使用

请根据来源仓库和你的 AI 工具说明安装此技能。先阅读技能文件和依赖要求,再添加到工作流。

查看来源 ↗使用指南

技能说明

React Testing

Overview

This skill provides comprehensive guidance for testing React applications using Vitest, React Testing Library, and Jest. Apply these patterns when writing unit tests, integration tests, and ensuring code quality.

Core Philosophy

  • Test behavior, not implementation: Focus on what users see and do
  • Avoid testing internal state: Test public APIs and user interactions
  • Write tests that give confidence: Catch real bugs, not false positives
  • Keep tests simple: Tests should be easier to understand than the code

Testing Tools

Vitest Configuration
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './src/test/setup.ts',
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      exclude: ['node_modules/', 'src/test/']
    }
  }
});
Test Setup
// src/test/setup.ts
import { expect, afterEach } from 'vitest';
import { cleanup } from '@testing-library/react';
import * as matchers from '@testing-library/jest-dom/matchers';

expect.extend(matchers);

afterEach(() => {
  cleanup();
});

Component Testing

Basic Component Test
import { render, screen } from '@testing-library/react';
import { expect, test } from 'vitest';
import { Button } from './Button';

test('renders button with label', () => {
  render(<Button label="Click me" onClick={vi.fn()} />);
  expect(screen.getByText('Click me')).toBeInTheDocument();
});

test('calls onClick when clicked', async () => {
  const handleClick = vi.fn();
  render(<Button label="Click me" onClick={handleClick} />);

  await userEvent.click(screen.getByRole('button'));
  expect(handleClick).toHaveBeenCalledTimes(1);
});

test('is disabled when prop is set', () => {
  render(<Button label="Click me" onClick={vi.fn()} disabled />);
  expect(screen.getByRole('button')).toBeDisabled();
});
Form Testing
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LoginForm } from './LoginForm';

test('submits form with email and password', async () => {
  const handleSubmit = vi.fn();
  render(<LoginForm onSubmit={handleSubmit} />);

  await userEvent.type(screen.getByLabelText('Email'), 'user@example.com');
  await userEvent.type(screen.getByLabelText('Password'), 'password123');
  await userEvent.click(screen.getByRole('button', { name: 'Login' }));

  await waitFor(() => {
    expect(handleSubmit).toHaveBeenCalledWith({
      email: 'user@example.com',
      password: 'password123'
    });
  });
});

test('shows validation error for invalid email', async () => {
  render(<LoginForm onSubmit={vi.fn()} />);

  await userEvent.type(screen.getByLabelText('Email'), 'invalid-email');
  await userEvent.click(screen.getByRole('button', { name: 'Login' }));

  expect(await screen.findByText('Invalid email address')).toBeInTheDocument();
});

Hook Testing

Custom Hook Test
import { renderHook, act } from '@testing-library/react';
import { expect, test } from 'vitest';
import { useCounter } from './useCounter';

test('increments counter', () => {
  const { result } = renderHook(() => useCounter());

  expect(result.current.count).toBe(0);

  act(() => {
    result.current.increment();
  });

  expect(result.current.count).toBe(1);
});

test('decrements counter', () => {
  const { result } = renderHook(() => useCounter(10));

  act(() => {
    result.current.decrement();
  });

  expect(result.current.count).toBe(9);
});
Hook with Dependencies
import { renderHook, waitFor } from '@testing-library/react';
import { useApi } from './useApi';

test('fetches data successfully', async () => {
  global.fetch = vi.fn(() =>
    Promise.resolve({
      ok: true,
      json: () => Promise.resolve({ name: 'John' })
    })
  ) as any;

  const { result } = renderHook(() => useApi('/api/user'));

  expect(result.current.loading).toBe(true);

  await waitFor(() => {
    expect(result.current.loading).toBe(false);
  });

  expect(result.current.data).toEqual({ name: 'John' });
  expect(result.current.error).toBeNull();
});

test('handles error', async () => {
  global.fetch = vi.fn(() =>
    Promise.reject(new Error('Network error'))
  );

  const { result } = renderHook(() => useApi('/api/user'));

  await waitFor(() => {
    expect(result.current.error).toBeTruthy();
  });

  expect(result.current.data).toBeUndefined();
});

Integration Testing

With Context
import { render, screen } from '@testing-library/react';
import { AuthProvider } from './AuthContext';
import { Dashboard } from './Dashboard';

const renderWithAuth = (ui: ReactElement, { user = null } = {}) => {
  return render(
    <AuthProvider value={{ user }}>
      {ui}
    </AuthProvider>
  );
};

test('shows dashboard when authenticated', () => {
  renderWithAuth(<Dashboard />, { user: { name: 'John' } });
  expect(screen.getByText('Welcome, John')).toBeInTheDocument();
});

test('redirects to login when not authenticated', () => {
  renderWithAuth(<Dashboard />);
  expect(screen.queryByText('Welcome')).not.toBeInTheDocument();
});
With Router
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { App } from './App';

test('navigates to profile page', async () => {
  render(
    <MemoryRouter initialEntries={['/profile']}>
      <App />
    </MemoryRouter>
  );

  expect(screen.getByText('User Profile')).toBeInTheDocument();
});

Mocking

API Mocking
import { rest } from 'msw';
import { setupServer } from 'msw/node';

const server = setupServer(
  rest.get('/api/users', (req, res, ctx) => {
    return res(
      ctx.json([
        { id: '1', name: 'John' },
        { id: '2', name: 'Jane' }
      ])
    );
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

test('displays users from API', async () => {
  render(<UserList />);

  expect(await screen.findByText('John')).toBeInTheDocument();
  expect(await screen.findByText('Jane')).toBeInTheDocument();
});
Component Mocking
import { vi } from 'vitest';

vi.mock('./HeavyComponent', () => ({
  HeavyComponent: () => <div>Mocked Component</div>
}));

test('renders page with mocked component', () => {
  render(<Dashboard />);
  expect(screen.getByText('Mocked Component')).toBeInTheDocument();
});

Accessibility Testing

import { axe, toHaveNoViolations } from 'jest-axe';

expect.extend(toHaveNoViolations);

test('has no accessibility violations', async () => {
  const { container } = render(<LoginForm onSubmit={vi.fn()} />);
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});

Best Practices

  1. Use semantic queries: getByRole, getByLabelText over getByTestId
  2. Test user behavior: Click, type, navigate like users do
  3. Avoid implementation details: Don't test state or props directly
  4. Use findBy for async: Automatically waits for elements
  5. Clean up: Use afterEach(cleanup) to reset DOM
  6. Mock external dependencies: APIs, timers, modules
  7. Test error states: Loading, error, empty states
  8. Keep tests focused: One assertion per test when possible
  9. Use descriptive test names: Describe what should happen
  10. Run tests in CI/CD: Ensure tests pass before deployment

使用示例

查看来源仓库中的使用示例与配置说明。

查看来源 ↗

来源历史

收录时间
2026-01-20
索引更新时间
2026-02-04
Branch
main

收录与同步日期属于本站记录。源码修改历史请查看来源仓库。

GitHub ↗

chat Comments (0)

chat_bubble_outline

No comments yet. Be the first to share your thoughts!