Как выполнять запросы к DOM в React Testing Library

DOM-запросы React Testing Library являются основой эффективного тестирования компонентов, однако многие разработчики испытывают трудности с выбором подходящего метода запроса для конкретных сценариев тестирования. Независимо от того, работаете ли вы с синхронными элементами, асинхронными компонентами или условным рендерингом, понимание того, когда использовать методы getBy, findBy и queryBy, может стать решающим фактором между надежными и нестабильными тестами.
Это руководство охватывает основные методы запросов React Testing Library, их поведенческие различия и практические примеры, которые помогут вам писать более надежные тесты. Вы изучите рекомендации по приоритетности запросов, распространенные ошибки, которых следует избегать, и реальные паттерны тестирования, повышающие надежность тестов.
Ключевые выводы
- Используйте запросы getBy для элементов, которые должны присутствовать сразу после рендеринга
- Используйте запросы findBy для элементов, которые появляются асинхронно, например, после API-вызовов или обновлений состояния
- Используйте запросы queryBy при тестировании отсутствия элементов в DOM
- Отдавайте приоритет запросам getByRole и getByLabelText для лучшего тестирования доступности
- Резервируйте getByTestId для сложных сценариев, где семантические запросы непрактичны
- Отлаживайте неудачные запросы с помощью screen.debug() и Testing Playground для лучшего выбора запросов
Начало работы с DOM-запросами React Testing Library
React Testing Library построена поверх DOM Testing Library, добавляя API, специально разработанные для тестирования React-компонентов. Установите её в ваш React-проект:
npm install --save-dev @testing-library/react @testing-library/jest-dom
DOM-запросы React Testing Library работают путем поиска элементов в дереве DOM отрендеренного компонента, аналогично тому, как пользователи взаимодействуют с вашим приложением. Вот базовый пример:
import { render, screen } from '@testing-library/react'
import LoginForm from './LoginForm'
test('renders login form', () => {
render(<LoginForm />)
const usernameInput = screen.getByLabelText('Username')
expect(usernameInput).toBeInTheDocument()
})
Ключевое отличие от DOM Testing Library заключается в том, что React Testing Library автоматически обрабатывает специфичные для React аспекты, такие как рендеринг компонентов, обновления состояния и очистка.
Понимание типов запросов React Testing Library
React Testing Library предоставляет три основных типа запросов, каждый с различным поведением для обработки отсутствующих элементов и временных аспектов.
Запросы getBy - синхронный выбор элементов
Запросы getBy возвращают элементы немедленно и выбрасывают ошибки, если элементы не найдены или найдено несколько совпадений:
// Один элемент - выбрасывает ошибку, если не найден или найдено несколько
const button = screen.getByRole('button', { name: 'Submit' })
// Несколько элементов - выбрасывает ошибку, если ни одного не найдено
const listItems = screen.getAllByRole('listitem')
Используйте запросы getBy, когда ожидаете, что элементы будут присутствовать в DOM сразу после рендеринга.
Запросы findBy - асинхронные DOM-запросы
Запросы findBy возвращают промисы и повторяют попытки до появления элементов или истечения времени ожидания (по умолчанию 1000мс):
// Ожидание появления асинхронного элемента
const successMessage = await screen.findByText('Profile updated successfully')
// Несколько асинхронных элементов
const loadedItems = await screen.findAllByTestId('product-card')
Используйте запросы findBy для элементов, которые появляются после API-вызовов, обновлений состояния или других асинхронных операций.
Запросы queryBy - условное тестирование элементов
Запросы queryBy возвращают null, когда элементы не найдены, что делает их идеальными для тестирования отсутствия элементов:
// Тест на отсутствие элемента
const errorMessage = screen.queryByText('Error occurred')
expect(errorMessage).not.toBeInTheDocument()
// Несколько элементов - возвращает пустой массив, если ничего не найдено
const hiddenElements = screen.queryAllByTestId('hidden-item')
expect(hiddenElements).toHaveLength(0)
Тип запроса | 0 совпадений | 1 совпадение | >1 совпадений | Асинхронный |
---|---|---|---|---|
getBy | Выбросить ошибку | Вернуть элемент | Выбросить ошибку | Нет |
queryBy | Вернуть null | Вернуть элемент | Выбросить ошибку | Нет |
findBy | Выбросить ошибку | Вернуть элемент | Выбросить ошибку | Да |
Руководство по приоритетности запросов React Testing Library
React Testing Library поощряет тестирование компонентов так, как с ними взаимодействуют пользователи. Это руководство по приоритетности поможет вам выбрать запросы, отражающие реальное поведение пользователей.
Запросы, ориентированные на доступность (getByRole, getByLabelText)
getByRole должен быть вашим первым выбором для большинства элементов:
// Кнопки, ссылки, элементы форм
const submitButton = screen.getByRole('button', { name: 'Create Account' })
const navigationLink = screen.getByRole('link', { name: 'About Us' })
// Заголовки с определенными уровнями
const pageTitle = screen.getByRole('heading', { level: 1 })
getByLabelText лучше всего работает для полей форм:
const emailInput = screen.getByLabelText('Email Address')
const passwordInput = screen.getByLabelText(/password/i)
Эти запросы обеспечивают работу ваших компонентов с вспомогательными технологиями.
Запросы на основе содержимого (getByText, getByPlaceholderText)
getByText находит элементы по их видимому текстовому содержимому:
// Точное соответствие текста
const welcomeMessage = screen.getByText('Welcome back, John!')
// Регулярное выражение для гибкого поиска
const errorText = screen.getByText(/something went wrong/i)
getByPlaceholderText помогает, когда метки недоступны:
const searchInput = screen.getByPlaceholderText('Search products...')
Когда использовать getByTestId
Резервируйте getByTestId для случаев, когда семантические запросы недостаточны:
// Динамическое содержимое, где текст изменяется
const userAvatar = screen.getByTestId('user-avatar')
// Сложные компоненты без четких ролей
const chartContainer = screen.getByTestId('sales-chart')
Добавляйте test ID экономно и предпочитайте семантические запросы, когда это возможно.
Распространенные ошибки DOM-запросов React Testing Library
Избегание нестабильных тестов с правильным выбором запросов
Проблема: использование запросов getBy для элементов, которые загружаются асинхронно:
// ❌ Нестабильный - элемент может быть еще не загружен
test('shows user profile', () => {
render(<UserProfile userId="123" />)
const userName = screen.getByText('John Doe') // Может не сработать
})
Решение: используйте findBy для асинхронных элементов:
// ✅ Надежный - ожидает появления элемента
test('shows user profile', async () => {
render(<UserProfile userId="123" />)
const userName = await screen.findByText('John Doe')
expect(userName).toBeInTheDocument()
})
Отладка неудачных запросов
Когда запросы не срабатывают, React Testing Library предоставляет полезные инструменты отладки:
// Посмотреть, что на самом деле находится в DOM
screen.debug()
// Получить предложения для лучших запросов
screen.getByRole('button') // Сообщение об ошибке предлагает доступные роли
Используйте Testing Playground для экспериментов с запросами на вашем реальном HTML.
Чрезмерная зависимость от Test ID
Проблема: использование getByTestId как метода запросов по умолчанию:
// ❌ Не ориентированный на пользователя
const button = screen.getByTestId('submit-button')
Решение: используйте семантические запросы, отражающие взаимодействие пользователя:
// ✅ Ориентированный на пользователя
const button = screen.getByRole('button', { name: 'Submit Form' })
Test ID должны быть вашим последним средством, а не первым выбором.
Реальные примеры React Testing Library
Вот практические примеры, показывающие различные методы запросов в действии:
Тестирование форм:
import { render, screen, fireEvent } from '@testing-library/react'
test('handles form submission', async () => {
render(<ContactForm />)
// Используйте getByLabelText для полей форм
const nameInput = screen.getByLabelText('Full Name')
const emailInput = screen.getByLabelText('Email')
const submitButton = screen.getByRole('button', { name: 'Send Message' })
// Заполните форму и отправьте
fireEvent.change(nameInput, { target: { value: 'John Doe' } })
fireEvent.change(emailInput, { target: { value: 'john@example.com' } })
fireEvent.click(submitButton)
// Ожидайте сообщение об успехе
const successMessage = await screen.findByText('Message sent successfully!')
expect(successMessage).toBeInTheDocument()
})
Тестирование состояния ошибки:
import { rest } from 'msw'
test('displays error when API fails', async () => {
// Имитация сбоя API
server.use(
rest.get('/api/users', (req, res, ctx) => {
return res(ctx.status(500))
})
)
render(<UserList />)
// Ожидайте появления сообщения об ошибке
const errorMessage = await screen.findByText(/failed to load users/i)
expect(errorMessage).toBeInTheDocument()
// Убедитесь, что состояние загрузки исчезло
const loadingSpinner = screen.queryByTestId('loading-spinner')
expect(loadingSpinner).not.toBeInTheDocument()
})
Заключение
Освоение DOM-запросов React Testing Library требует понимания того, когда использовать getBy для немедленных элементов, findBy для асинхронного содержимого и queryBy для тестирования отсутствия элементов. Отдавайте приоритет запросам, ориентированным на доступность, таким как getByRole и getByLabelText, и резервируйте getByTestId для крайних случаев, где семантические запросы недостаточны.
Ключ к надежным тестам заключается в выборе запросов, которые отражают то, как пользователи взаимодействуют с вашими компонентами. Такой подход создает тесты, которые одновременно надежны и поддерживаемы, выявляя реальные проблемы, с которыми сталкиваются пользователи, оставаясь при этом устойчивыми к изменениям в реализации.
Часто задаваемые вопросы
Используйте запросы findBy при тестировании элементов, которые появляются после асинхронных операций, таких как API-вызовы, setTimeout или обновления состояния. Запросы findBy автоматически повторяют попытки до появления элемента или истечения времени ожидания, предотвращая нестабильные тесты.
Запросы getByTestId не отражают то, как пользователи взаимодействуют с вашим приложением. Пользователи не видят test ID - они взаимодействуют с кнопками, читают текст и используют метки форм. Семантические запросы, такие как getByRole и getByText, создают более реалистичные и поддерживаемые тесты.
Используйте запросы queryBy, которые возвращают null, когда элементы не найдены, вместо выбрасывания ошибок. Например: expect(screen.queryByText('Error message')).not.toBeInTheDocument().
Запросы getAllBy возвращают массивы элементов немедленно и выбрасывают ошибки, если элементы не найдены. Запросы findAllBy возвращают промисы, которые разрешаются в массивы и ожидают появления хотя бы одного элемента перед разрешением.
Используйте screen.debug() для просмотра текущей структуры DOM, проверьте сообщения об ошибках для предложений запросов и попробуйте Testing Playground для экспериментов с различными подходами к запросам на вашем реальном HTML.