12k
All articles

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

Сравнение методов getBy, findBy и queryBy в React Testing Library для написания надёжных тестов компонентов: синхронные, асинхронные и условные элементы DOM.

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

Listen to your bugs 🧘, with OpenReplay

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

We use cookies to improve your experience. By using our site, you accept cookies.