Как выполнять запросы к DOM в React Testing Library
Сравнение методов getBy, findBy и queryBy в React Testing Library для написания надёжных тестов компонентов: синхронные, асинхронные и условные элементы DOM.
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 вместо запросов getBy?
Используйте запросы findBy при тестировании элементов, которые появляются после асинхронных операций, таких как API-вызовы, setTimeout или обновления состояния. Запросы findBy автоматически повторяют попытки до появления элемента или истечения времени ожидания, предотвращая нестабильные тесты.
Почему React Testing Library рекомендует избегать getByTestId?
Запросы getByTestId не отражают то, как пользователи взаимодействуют с вашим приложением. Пользователи не видят test ID - они взаимодействуют с кнопками, читают текст и используют метки форм. Семантические запросы, такие как getByRole и getByText, создают более реалистичные и поддерживаемые тесты.
Как тестировать, что элемент не отрендерен?
Используйте запросы queryBy, которые возвращают null, когда элементы не найдены, вместо выбрасывания ошибок. Например: expect(screen.queryByText('Error message')).not.toBeInTheDocument().
В чем разница между запросами getAllBy и findAllBy?
Запросы getAllBy возвращают массивы элементов немедленно и выбрасывают ошибки, если элементы не найдены. Запросы findAllBy возвращают промисы, которые разрешаются в массивы и ожидают появления хотя бы одного элемента перед разрешением.
Как можно отладить неудачные запросы React Testing Library?
Используйте screen.debug() для просмотра текущей структуры DOM, проверьте сообщения об ошибках для предложений запросов и попробуйте Testing Playground для экспериментов с различными подходами к запросам на вашем реальном HTML.