screen object как основной API

React Testing Library (RTL) предлагает инструменты для тестирования компонентов, ориентируясь на поведение пользователя, а не на реализацию. Одним из ключевых элементов является объект screen, который служит универсальным интерфейсом для поиска элементов в виртуальном DOM, созданном в тестах. Использование screen обеспечивает более чистый и читаемый код, так как избавляет от необходимости хранить ссылки на рендеринг компонента в каждой тестовой функции.


Основы использования screen

После рендера компонента с помощью render() доступ к элементам осуществляется через screen:

import { render, screen } from '@testing-library/react';
import Button from './Button';

test('кнопка отображается с текстом', () => {
  render(<Button>Нажми меня</Button>);
  const buttonElement = screen.getByText('Нажми меня');
  expect(buttonElement).toBeInTheDocument();
});

Ключевые моменты:

  • screen предоставляет унифицированный доступ ко всем методам поиска элементов.
  • Методы getBy*, queryBy* и findBy* можно вызывать напрямую через screen, без деструктуризации объекта, возвращаемого render().

Разновидности методов поиска

screen поддерживает несколько категорий методов, отличающихся поведением при отсутствии элемента.

  1. getBy* – выбрасывает ошибку, если элемент не найден.

    screen.getByRole('button', { name: /отправить/i });
  2. queryBy* – возвращает null, если элемент отсутствует.

    expect(screen.queryByText('Ошибка')).toBeNull();
  3. findBy* – асинхронный поиск, возвращает Promise, полезен при работе с асинхронными эффектами.

    await screen.findByText('Данные загружены');

Методы поиска делятся на категории по типу селектора:

  • Text-based: getByText, queryByText, findByText.
  • Role-based: getByRole, queryByRole, findByRole.
  • Label-based: getByLabelText, queryByLabelText, findByLabelText.
  • Placeholder-based: getByPlaceholderText.
  • Alt/Title attributes: getByAltText, getByTitle.
  • TestId: getByTestId (использовать как запасной вариант).

Почему screen предпочтительнее деструктуризации render()

Ранее стандартным подходом было деструктурировать результат render:

const { getByText } = render(<Button>Нажми меня</Button>);
getByText('Нажми меня');

Недостатки этого подхода:

  • Каждый тест должен создавать локальные ссылки на методы поиска.
  • Увеличивается вероятность ошибок при рефакторинге.
  • Меньше читаемость и согласованность между тестами.

Использование screen:

render(<Button>Нажми меня</Button>);
screen.getByText('Нажми меня');
  • Код становится однородным и самодокументируемым.
  • Методы поиска доступны глобально после рендера.
  • Облегчается переход к асинхронным тестам.

Работа с асинхронными элементами

screen активно применяется для тестирования компонентов, где содержимое появляется после асинхронных операций (например, fetch-запросов):

render(<AsyncComponent />);
const message = await screen.findByText(/данные загружены/i);
expect(message).toBeInTheDocument();
  • findBy* автоматически повторяет попытку поиска до таймаута (по умолчанию 1000 мс).
  • Можно комбинировать с waitFor, если нужно дождаться состояния компонента:
await waitFor(() => {
  expect(screen.getByRole('alert')).toHaveTextContent('Ошибка загрузки');
});

Комбинирование screen с пользовательскими событиями

Для имитации взаимодействия с пользователем используют userEvent:

import userEvent from '@testing-library/user-event';

render(<Button>Кликни меня</Button>);
const button = screen.getByText('Кликни меня');
userEvent.click(button);
expect(button).toBeDisabled();
  • screen обеспечивает стабильную ссылку на элементы.
  • Взаимодействие через userEvent имитирует реальные события браузера.
  • Тесты становятся ориентированными на поведение, а не на реализацию.

Практические рекомендации при работе с screen

  1. Предпочтение ролям (getByRole):

    • Поиск по ролям более устойчив к изменениям текста.

    • Пример:

      screen.getByRole('button', { name: /сохранить/i });
  2. Избегать getByTestId, если возможно:

    • Лучше использовать текст, роли или метки.
    • TestId оставлять только для сложных случаев, где другие методы не применимы.
  3. Использовать screen.debug() для отладки:

    • Выводит текущее состояние DOM.

    • Полезно для понимания структуры при сложных компонентах:

      screen.debug();
  4. Проверка отсутствующих элементов:

    • queryBy* идеально подходит для утверждений об отсутствии:

      expect(screen.queryByText('Загрузка')).toBeNull();
  5. Асинхронные проверки:

    • Использовать findBy* или waitFor, чтобы избежать flaky-тестов.

Заключение по screen

screen в React Testing Library выступает центральным API для поиска и взаимодействия с элементами виртуального DOM. Он обеспечивает:

  • единый интерфейс для всех методов поиска;
  • более читаемый и поддерживаемый код;
  • упрощение работы с асинхронными компонентами;
  • ориентированность на поведение пользователя, а не на детали реализации.

Понимание и грамотное использование screen позволяет создавать надежные, понятные и легко поддерживаемые тесты для React-приложений.