React

Тестирование: RTL и Playwright

Тест проверяет, что у компонента в состоянии лежит count === 1, лезет в приватный метод и сверяет имя CSS-класса. Через неделю разработчик переименовывает переменную состояния и меняет вёрстку - функциональность та же, но десять тестов краснеют. Тесты, привязанные к реализации, ломаются от рефакторинга и ничего не говорят о том, работает ли продукт для пользователя. React Testing Library перевернула подход: проверяй то, что видит и делает пользователь, а не то, как устроены внутренности. Тогда рефакторинг проходит молча, а падение теста означает реальную регрессию.

  • Компонентные тесты дизайн-систем: кнопка, модалка, форма проверяются по доступной роли и видимому тексту
  • E2E-сценарии на Playwright: вход, оформление заказа, оплата прогоняются в реальном браузере
  • Асинхронные интерфейсы с загрузкой и Suspense, где тест ждёт появления данных, а не таймера
  • MSW мокирует сетевой слой, поэтому тесты не ходят в настоящий backend и стабильны
  • CI-пайплайны, где быстрые тесты RTL и медленные e2e Playwright дополняют друг друга

Предварительные знания

  • Модель рендера: компонент отображает UI из состояния и пропсов
  • Состояние и обработчики событий, обновление интерфейса по действию
  • Базовое понимание промисов и асинхронной загрузки данных
  • Модель рендера

Сдвиг от тестов реализации к тестам поведения

Ранние инструменты вроде Enzyme поощряли заглядывать внутрь компонента: проверять состояние, вызывать методы, искать узлы по именам классов. Такие тесты ломались от любого рефакторинга, даже когда поведение не менялось. В 2018 Кент Доддс выпустил React Testing Library с принципом: чем больше тест похож на то, как продукт используют, тем больше он внушает доверия. Запросы строятся по доступной роли и тексту, как у реального пользователя и скринридера. Параллельно Microsoft развивала Playwright (2020) для e2e в настоящем браузере, а Mock Service Worker дал перехват сети на уровне сервис-воркера.

Поведение вместо реализации: роли и user-event

Главный принцип RTL: тест должен взаимодействовать с компонентом так же, как пользователь, и проверять то, что пользователь видит. Не внутреннее состояние и не имена классов, а отрисованный результат. Поэтому элементы ищут по доступной роли (кнопка, заголовок, поле ввода) и по видимому тексту, а не по селекторам, привязанным к деталям вёрстки.

user-event имитирует поведение пользователя ближе к реальности, чем низкоуровневый fireEvent. Клик через user-event это последовательность реальных событий: наведение, нажатие, отпускание, фокус. Ввод текста печатает посимвольно, запуская обработчики на каждом символе. Это ловит баги, которые единичный fireEvent.change пропустил бы.

ЗапросКогда вернётПрименение
getByСразу, бросает ошибку если нетЭлемент точно есть на экране
queryByСразу, null если нетПроверка отсутствия элемента
findByПромис, ждёт появленияАсинхронный элемент после загрузки

Поиск по тестовому идентификатору data-testid это запасной вариант, а не основной. Приоритет за ролью и текстом: такие запросы заодно проверяют доступность интерфейса. Если элемент не находится по роли, часто это сигнал, что разметка недоступна для скринридеров.

Почему RTL советует искать элементы по роли и тексту, а не по именам CSS-классов или состоянию компонента?

Асинхронность, Suspense и мокирование сети через MSW

Асинхронный интерфейс сначала показывает загрузку, потом данные. Тест не должен ждать фиксированную задержку - это хрупко. Вместо этого используют findBy или waitFor: они опрашивают DOM, пока нужный элемент не появится, с разумным таймаутом. То же касается Suspense: тест проверяет сначала фолбэк, затем дождавшись данных - результат.

Чтобы тест не ходил в настоящий backend, сеть мокируют. Mock Service Worker перехватывает запросы на уровне сетевого слоя по тем же URL, что и в проде. Компонент делает обычный fetch и не знает, что ответ пришёл от мока. Один набор обработчиков MSW переиспользуется и в тестах, и в storybook, и в локальной разработке.

MSW позволяет переопределить обработчик в отдельном тесте через server.use, чтобы вернуть ошибку 500 или пустой список. Так проверяются ветки загрузки, ошибки и пустого состояния без изменения компонента - меняется только ответ сети.

Как правильно дождаться асинхронных данных в тесте RTL?

Playwright: сквозные сценарии в реальном браузере

RTL проверяет компоненты в смоделированном окружении быстро, но не запускает реальный браузер целиком. Сквозные сценарии - вход, оформление заказа, оплата - проверяет Playwright: он управляет настоящим Chromium, Firefox и WebKit, кликает, печатает и ждёт так же, как пользователь. Это другой уровень пирамиды тестов: их меньше, они медленнее, но покрывают всю цепочку от UI до backend.

Playwright построен на авто-ожидании: каждое действие и проверка сами ждут, пока элемент станет видимым и готовым, без ручных задержек. Локаторы здесь, как и в RTL, ориентированы на роль и доступный текст, поэтому стиль запросов узнаваем. Это снижает нестабильность, главную болезнь e2e-тестов прошлого.

  • React Testing Library — Компонентные и интеграционные тесты в смоделированном DOM. Быстрые, их много. Проверяют поведение отдельных частей UI.
  • Playwright — Сквозные тесты в настоящем браузере через всю систему. Медленнее, их меньше. Проверяют критичные пользовательские сценарии целиком.

Здоровая стратегия это сочетание уровней: много быстрых тестов RTL на отдельные компоненты и поведение, и небольшой набор e2e Playwright на ключевые сценарии. Дублировать всю функциональность в медленных e2e нецелесообразно - они дороги в поддержке и времени.

Как соотносятся роли React Testing Library и Playwright в стратегии тестирования?

Связь с другими темами

Этот урок про тестирование. Рядом стоят соседние темы:

  • Модель рендера — Тесты проверяют наблюдаемый результат рендера из состояния, а не внутреннюю механику
  • Формы с RHF и Zod — Формы это частый объект тестирования: ввод через user-event и проверка сообщений валидации

Итог

  • React Testing Library проверяет наблюдаемое поведение, а не реализацию, поэтому рефакторинг не ломает тесты без причины
  • Запросы строятся по доступной роли и видимому тексту (getByRole, getByText), как взаимодействует реальный пользователь
  • user-event имитирует действия пользователя ближе к реальности, чем низкоуровневый fireEvent
  • Асинхронность и Suspense тестируются ожиданием появления элемента (findBy, waitFor), а не фиксированными задержками
  • MSW мокирует сеть на уровне перехвата запросов, так что тесты не зависят от настоящего backend и стабильны
  • Playwright прогоняет e2e в реальном браузере: RTL покрывает компоненты быстро, Playwright проверяет сквозные сценарии

Связанные уроки

  • rc-10-render-mental-model — Понимание того, что компонент рендерит из состояния, лежит в основе проверки наблюдаемого поведения
  • rc-39-forms-zod — Формы это типичный объект тестирования: ввод через user-event и проверка сообщений валидации
Тестирование: RTL и Playwright

0

1

Войти