Back

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

Как выполнять запросы к 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.

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers