Timeout и обработка ошибок при ожидании

React Testing Library (RTL) предоставляет мощные инструменты для тестирования компонентов, но при работе с асинхронным кодом важное значение имеют тайм-ауты и корректная обработка ошибок при ожидании. Неправильное использование этих механизмов может приводить к нестабильным тестам, долгому выполнению и скрытым багам.


Асинхронное ожидание элементов

Для работы с асинхронными компонентами RTL предлагает функции вида findBy*, которые возвращают промис:

const button = await screen.findByRole('button', { name: /submit/i });

Эта функция автоматически повторяет поиск элемента в течение определённого времени (по умолчанию 1000 мс). Если элемент не найден, промис отклоняется, и тест завершится с ошибкой.


Настройка тайм-аутов

Тайм-аут можно настраивать через опцию { timeout: number }:

const message = await screen.findByText('Загрузка завершена', { timeout: 3000 });

Ключевой момент: слишком маленький тайм-аут приводит к ложным отрицательным результатам, слишком большой — замедляет тесты. Оптимальное значение зависит от ожидаемой длительности асинхронной операции, сетевых задержек и производительности тестового окружения.

Для глобальной настройки тайм-аутов можно использовать jest.setTimeout:

jest.setTimeout(5000); // увеличивает тайм-аут для всех тестов в файле

waitFor и контроль ошибок

Функция waitFor используется для явного ожидания условия:

await waitFor(() => {
  expect(screen.getByText('Данные загружены')).toBeInTheDocument();
}, { timeout: 2000 });

waitFor повторяет выполнение переданного колбэка до тех пор, пока он не выполнится без ошибок или не истечёт тайм-аут.

Обработка ошибок при ожидании: если внутри waitFor возникает исключение, RTL продолжает попытки до истечения тайм-аута. После этого промис отклоняется с ошибкой, содержащей причину:

Error: Timed out in waitFor.

Можно использовать блок try/catch для явной обработки ошибок и логирования:

try {
  await waitFor(() => expect(screen.getByText('Ошибка загрузки')).toBeInTheDocument(), { timeout: 2500 });
} catch (error) {
  console.error('Элемент не появился вовремя', error);
}

Различия между getBy, queryBy и findBy

  • getBy* — синхронный поиск, выбрасывает ошибку сразу, если элемент не найден.
  • queryBy* — синхронный поиск, возвращает null, если элемент отсутствует.
  • findBy* — асинхронный поиск с повторением до тайм-аута.

Для асинхронных сценариев findBy* и waitFor являются ключевыми инструментами. Правильное использование timeout предотвращает нестабильность тестов.


Комбинирование waitFor с проверками состояния

Иногда требуется дождаться изменения состояния компонента или завершения побочных эффектов:

await waitFor(() => {
  expect(screen.queryByText('Загрузка...')).not.toBeInTheDocument();
});

Здесь используется queryBy* для проверки отсутствия элемента. Такой подход предотвращает ложные срабатывания, когда элемент ещё не удалён из DOM.


Использование пользовательских таймеров

При работе с тайм-аутами полезно комбинировать jest.useFakeTimers и act:

jest.useFakeTimers();

act(() => {
  jest.advanceTimersByTime(3000);
});

await waitFor(() => expect(screen.getByText('Готово')).toBeInTheDocument());

Это ускоряет тесты, позволяя контролировать время, не дожидаясь реальных задержек.


Лучшие практики работы с тайм-аутами и ошибками

  1. Использовать минимально необходимый тайм-аут для асинхронных операций.
  2. Предпочитать findBy* для ожидания появления элементов.
  3. Использовать waitFor для проверки изменений состояния или скрытия элементов.
  4. Оборачивать ожидания в try/catch при необходимости детального логирования ошибок.
  5. Для сложных сценариев симулировать таймеры через jest.useFakeTimers для ускорения тестов.

Правильное управление тайм-аутами и обработкой ошибок позволяет создавать надёжные, быстрые и предсказуемые тесты, что особенно важно при тестировании сложных React-компонентов с асинхронными эффектами.