12. React Testing Library

Философия RTL
Заголовок раздела «Философия RTL»“Test the way your users use it, not implementation details”
React Testing Library (RTL) тестирует поведение компонента с точки зрения пользователя, а не внутреннюю реализацию.
npm install --save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-eventНастройка
Заголовок раздела «Настройка»import '@testing-library/jest-dom';
// jest.config.jsmodule.exports = { testEnvironment: 'jsdom', setupFilesAfterFramework: ['<rootDir>/src/setupTests.js'],};
// vitest.config.tsexport default defineConfig({ test: { environment: 'jsdom', setupFiles: ['./src/setupTests.ts'], },})Первый тест
Заголовок раздела «Первый тест»export function Button({ onClick, children, disabled = false }) { return ( <button onClick={onClick} disabled={disabled}> {children} </button> );}
// Button.test.tsximport { render, screen } from '@testing-library/react';import userEvent from '@testing-library/user-event';import { Button } from './Button';
test('renders button with text', () => { render(<Button>Click me</Button>);
const button = screen.getByRole('button', { name: /click me/i }); expect(button).toBeInTheDocument();});
test('calls onClick when clicked', async () => { const user = userEvent.setup(); const handleClick = jest.fn();
render(<Button onClick={handleClick}>Submit</Button>);
await user.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);});
test('button is disabled', () => { render(<Button disabled>Submit</Button>); expect(screen.getByRole('button')).toBeDisabled();});jest-dom Matchers
Заголовок раздела «jest-dom Matchers»// DOM-специфичные матчеры (из @testing-library/jest-dom)
expect(element).toBeInTheDocument(); // элемент в DOMexpect(element).toBeVisible(); // элемент виденexpect(element).toBeDisabled(); // disabledexpect(element).toBeEnabled(); // не disabledexpect(element).toBeChecked(); // checkbox/radio checkedexpect(element).toHaveValue('text'); // значение inputexpect(element).toHaveTextContent('hello'); // текст элементаexpect(element).toHaveClass('btn-primary'); // CSS классexpect(element).toHaveAttribute('href', '/home'); // атрибутexpect(element).toHaveFocus(); // в фокусеexpect(element).toBeRequired(); // required атрибутexpect(element).toBeValid(); // форма валиднаexpect(element).toBeInvalid(); // форма невалиднаТестирование компонентов с пропсами
Заголовок раздела «Тестирование компонентов с пропсами»interface User { name: string; email: string; role: 'admin' | 'user'; avatar?: string;}
export function UserCard({ user }: { user: User }) { return ( <div data-testid="user-card"> {user.avatar ? ( <img src={user.avatar} alt={`${user.name} avatar`} /> ) : ( <div data-testid="avatar-placeholder">{user.name[0]}</div> )} <h2>{user.name}</h2> <p>{user.email}</p> {user.role === 'admin' && <span data-testid="admin-badge">Admin</span>} </div> );}
// UserCard.test.tsxdescribe('UserCard', () => { const defaultUser = { name: 'Alice', role: 'user' as const, };
test('renders user info', () => { render(<UserCard user={defaultUser} />);
expect(screen.getByText('Alice')).toBeInTheDocument(); });
test('shows admin badge for admins', () => { render(<UserCard user={{ ...defaultUser, role: 'admin' }} />); expect(screen.getByTestId('admin-badge')).toBeInTheDocument(); });
test('hides admin badge for regular users', () => { render(<UserCard user={defaultUser} />); expect(screen.queryByTestId('admin-badge')).not.toBeInTheDocument(); });
test('shows avatar image when provided', () => { render(<UserCard user={{ ...defaultUser, avatar: '/img/alice.jpg' }} />);
const img = screen.getByAltText('Alice avatar'); expect(img).toHaveAttribute('src', '/img/alice.jpg'); });
test('shows placeholder when no avatar', () => { render(<UserCard user={defaultUser} />); expect(screen.getByTestId('avatar-placeholder')).toHaveTextContent('A'); });});Практические задания
Заголовок раздела «Практические задания»- Установи RTL и напиши тесты для компонента
Alert(info/warning/error) - Протестируй компонент
Badgeс разными вариантами отображения - Протестируй список
UserList— отрисовку нескольких элементов и пустого состояния
- RTL тестирует поведение, не реализацию
- getByRole — приоритетный способ поиска элементов
- jest-dom добавляет удобные DOM-матчеры
- data-testid — только когда нет семантического способа найти элемент