Написание тестов для React-компонентов с использованием Jest — ключевой навык современного фронтенд-разработчика. Рассмотрим полный процесс от настройки до продвинутых техник.
Для тестирования React-компонентов нам понадобятся:
Установка (если проект не на CRA):
npm install --save-dev @testing-library/react @testing-library/jest-dom
Рассмотрим компонент Button.js
:
function Button({ onClick, children }) {
return <button onClick={onClick}>{children}</button>;
}
Тест для этого компонента (Button.test.js
):
import { render, screen, fireEvent } from '@testing-library/react';
import Button from './Button';
test('renders button with text and handles click', () => {
// 1. Мок-функция для обработчика
const handleClick = jest.fn();
// 2. Рендер компонента
render(<Button onClick={handleClick}>Click me</Button>);
// 3. Поиск элемента
const buttonElement = screen.getByText(/click me/i);
// 4. Проверка наличия
expect(buttonElement).toBeInTheDocument();
// 5. Симуляция клика
fireEvent.click(buttonElement);
// 6. Проверка вызова обработчика
expect(handleClick).toHaveBeenCalledTimes(1);
});
test('applies correct className', () => {
render(<Button className="primary">Submit</Button>);
const button = screen.getByRole('button');
expect(button).toHaveClass('primary');
});
Для компонента с состоянием:
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<span data-testid="count">{count}</span>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}
test('increments counter', () => {
render(<Counter />);
const countElement = screen.getByTestId('count');
const button = screen.getByText('Increment');
expect(countElement).toHaveTextContent('0');
fireEvent.click(button);
expect(countElement).toHaveTextContent('1');
});
Для тестирования кастомных хуков используем @testing-library/react-hooks
:
npm install @testing-library/react-hooks
Пример теста:
import { renderHook, act } from '@testing-library/react-hooks';
import useCounter from './useCounter';
test('should increment counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
Полезно для обнаружения неожиданных изменений в компонентах:
import renderer from 'react-test-renderer';
test('Button snapshot', () => {
const tree = renderer
.create(<Button>Save</Button>)
.toJSON();
expect(tree).toMatchSnapshot();
});
Пример теста для компонента с API-запросом:
import { render, screen, waitFor } from '@testing-library/react';
import UserProfile from './UserProfile';
test('loads and displays user data', async () => {
// Мокаем API-запрос
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ name: 'John Doe' }),
})
);
render(<UserProfile userId="123" />);
// Проверяем состояние загрузки
expect(screen.getByText(/loading/i)).toBeInTheDocument();
// Ждем появления данных
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
});
});
Тестируйте поведение, а не реализацию
Фокус на том, что делает компонент, а не как он устроен внутри
Используйте правильные queries
Предпочитайте getByRole
вместо getByTestId
где возможно
Избегайте лишних снапшотов
Снапшоты полезны, но не должны заменять осмысленные assertions
Держите тесты изолированными
Каждый тест должен работать независимо от других
Для тестирования React-компонентов с Jest мы используем комбинацию Jest и React Testing Library. Основные шаги: рендер компонента, поиск элементов в DOM, взаимодействие с элементами и проверка результатов. Правильно написанные тесты помогают находить ошибки на ранних этапах и делают код более надежным.