Зачем тестировать React-компоненты
▸Преимущества тестирования
Тесты ловят баги до попадания в продакшн, позволяют безопасно рефакторить код и документируют поведение компонентов. Testing Library поощряет тестирование по поведению, а не по реализации.
Настройка Jest
▸Установка
1npm install --save-dev jest @testing-library/react @testing-library/jest-dom jest-environment-jsdom
▸Конфигурация jest.config.js
1module.exports = {2 testEnvironment: 'jsdom',3 setupFilesAfterSetup: ['@testing-library/jest-dom'],4 moduleNameMapper: {5 '^@/(.*)$': '<rootDir>/src/$1',6 },7};
Первый тест
▸Тест компонента
1import { render, screen } from '@testing-library/react';2import { Button } from './Button';34test('renders button with text', () => {5 render(<Button>Нажми меня</Button>);6 const button = screen.getByRole('button', { name: /нажми меня/i });7 expect(button).toBeInTheDocument();8});
▸Тест клика
1test('calls onClick when clicked', () => {2 const handleClick = jest.fn();3 render(<Button onClick={handleClick}>Кликни</Button>);4 fireEvent.click(screen.getByRole('button'));5 expect(handleClick).toHaveBeenCalledTimes(1);6});
Поиск элементов
▸getByRole
Предпочтительный способ — поиск по ARIA роли:
1screen.getByRole('button');2screen.getByRole('textbox');3screen.getByRole('heading', { name: /заголовок/i });
▸getByText
Поиск по тексту:
1screen.getByText('Привет мир');2screen.getByText(/hello/i); // regex
▸queryByRole
Для проверки отсутствия элемента:
1expect(screen.queryByRole('alert')).not.toBeInTheDocument();
Async тесты
▸Тест с асинхронным поведением
1import { render, screen, waitFor } from '@testing-library/react';2import { UserProfile } from './UserProfile';34test('loads and displays user name', async () => {5 render(<UserProfile userId={1} />);6 expect(screen.getByText('Загрузка...')).toBeInTheDocument();7 await waitFor(() => {8 expect(screen.getByText('John Doe')).toBeInTheDocument();9 });10});
▸findBy (shortcut для waitFor + getBy)
1test('loads user', async () => {2 render(<UserProfile userId={1} />);3 const name = await screen.findByText('John Doe');4 expect(name).toBeInTheDocument();5});
Мокирование
▸Мок API запросов
1beforeEach(() => {2 global.fetch = jest.fn(() =>3 Promise.resolve({4 json: () => Promise.resolve({ name: 'John' }),5 })6 );7});89afterEach(() => {10 jest.restoreAllMocks();11});1213test('fetches user data', async () => {14 render(<UserProfile userId={1} />);15 expect(global.fetch).toHaveBeenCalledWith('/api/users/1');16});
▸Мок модулей
1jest.mock('./api', () => ({2 fetchUser: jest.fn(() => Promise.resolve({ name: 'John' })),3}));
▸Мок хуков
1jest.mock('react-router-dom', () => ({2 ...jest.requireActual('react-router-dom'),3 useNavigate: () => jest.fn(),4}));
Тестирование хуков
▸renderHook
1import { renderHook, act } from '@testing-library/react';2import { useCounter } from './useCounter';34test('increments count', () => {5 const { result } = renderHook(() => useCounter(0));6 act(() => result.current.increment());7 expect(result.current.count).toBe(1);8});
Тестирование форм
▸Тест формы ввода
1test('submits form with user input', async () => {2 const onSubmit = jest.fn();3 render(<LoginForm onSubmit={onSubmit} />);45 fireEvent.change(screen.getByLabelText(/email/i), {6 target: { value: 'test@example.com' },7 });8 fireEvent.change(screen.getByLabelText(/пароль/i), {9 target: { value: 'password123' },10 });11 fireEvent.click(screen.getByRole('button', { name: /войти/i }));1213 expect(onSubmit).toHaveBeenCalledWith({14 email: 'test@example.com',15 password: 'password123',16 });17});
Snapshot тесты
1test('matches snapshot', () => {2 const { container } = render(<Button>Тест</Button>);3 expect(container).toMatchSnapshot();4});
Best Practices
▸Тестируйте поведение, а не реализацию
Не тестируйте внутреннее состояние компонента. Тестируйте что компонент делает с точки зрения пользователя.
▸Используйте userEvent вместо fireEvent
1import userEvent from '@testing-library/user-event';23test('type and submit', async () => {4 const user = userEvent.setup();5 render(<Form />);6 await user.type(screen.getByRole('textbox'), 'Hello');7 await user.click(screen.getByRole('button'));8});
▸Избегайте чрезмерного количества snapshot тестов
Snapshot тесты хрупкие. Используйте их для визуальных компонентов, а не для логики.
Заключение
Jest + Testing Library — стандарт тестирования React. Фокусируйтесь на поведении компонентов, используйте правильные методы поиска и мокайте внешние зависимости. Написание тестов делает код надежнее и проще в поддержке.