waitFor: ожидание изменений в DOM

waitFor — это утилита из React Testing Library, предназначенная для ожидания асинхронных изменений в DOM. Она особенно полезна при тестировании компонентов, которые обновляют интерфейс после выполнения асинхронных операций, таких как запросы к API, таймеры или события пользователя.


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

Сигнатура функции:

await waitFor(callback, options?)
  • callback — функция, внутри которой проверяется состояние DOM. Если условие не выполняется, waitFor будет повторно вызывать callback до истечения таймаута.

  • options — объект с настройками:

    • timeout — максимальное время ожидания (по умолчанию 1000 мс).
    • interval — интервал между проверками состояния (по умолчанию 50 мс).
    • onTimeout — функция, вызываемая при превышении таймаута.

Простейший пример:

import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import MyComponent from './MyComponent';

test('кнопка появляется после клика', async () => {
  render(<MyComponent />);
  userEvent.click(screen.getByText('Показать кнопку'));

  await waitFor(() => {
    expect(screen.getByText('Новая кнопка')).toBeInTheDocument();
  });
});

В этом примере waitFor ожидает появления элемента в DOM после асинхронного обновления.


Основные принципы работы

  1. Повторное выполнение колбэка waitFor выполняет переданный колбэк до тех пор, пока:

    • не будет выполнено условие проверки (например, expect(element).toBeInTheDocument()), либо
    • не истечёт таймаут.
  2. Обработка ошибок Если внутри callback возникает ошибка (например, элемент ещё не существует), waitFor перехватывает её и продолжает повторные попытки. После превышения таймаута ошибка выбрасывается снова.

  3. Асинхронное ожидание waitFor возвращает Promise, поэтому его необходимо использовать с await внутри асинхронной функции test или it.


Практические советы

  • Не использовать waitFor без необходимости Для синхронных изменений DOM лучше использовать прямые проверки. waitFor оправдан только при асинхронных обновлениях.

  • Избегать сложной логики внутри callback Колбэк должен содержать только проверки состояния DOM. Логика изменения данных должна оставаться в тестируемом компоненте.

  • Настройка таймаута Для операций с длительным временем отклика (например, запросы к реальному API) стоит увеличить timeout:

    await waitFor(
      () => expect(screen.getByText('Данные загружены')).toBeInTheDocument(),
      { timeout: 3000 }
    );
  • Комбинация с findBy Методы findBy* (findByText, findByRole) используют waitFor под капотом и являются удобной альтернативой для одиночных асинхронных проверок:

    const button = await screen.findByText('Новая кнопка');
    expect(button).toBeInTheDocument();
  • Избегать ожидания постоянного состояния waitFor не предназначен для непрерывного отслеживания DOM. Он завершает работу, как только callback перестаёт выбрасывать ошибку или истекает таймаут.


Расширенные возможности

  • onTimeout — позволяет задать кастомное сообщение при превышении таймаута:

    await waitFor(
      () => expect(screen.getByText('Готово')).toBeInTheDocument(),
      {
        timeout: 2000,
        onTimeout: () => console.error('Элемент не появился за 2 секунды')
      }
    );
  • interval — позволяет контролировать частоту проверок. Уменьшение интервала делает проверку более «чуткой», увеличение снижает нагрузку:

    await waitFor(
      () => expect(screen.getByText('Прогресс')).toBeInTheDocument(),
      { interval: 100 }
    );
  • Использование с асинхронными запросами Для компонентов, которые получают данные через fetch или axios, waitFor позволяет безопасно дождаться обновления DOM после завершения промиса:

    test('данные загружаются и отображаются', async () => {
      render(<DataComponent />);
      await waitFor(() => expect(screen.getByText('Загружено')).toBeInTheDocument());
    });

Распространённые ошибки

  1. Преждевременный вызов expect без waitFor Проверка асинхронного изменения до его завершения приведёт к ошибке. Решение — обернуть expect в waitFor.

  2. Слишком короткий таймаут Асинхронные операции могут занимать больше времени, чем задано по умолчанию. Таймаут стоит подбирать с запасом.

  3. Возврат значения из callback waitFor игнорирует возвращаемое значение колбэка, проверяется только отсутствие выброшенной ошибки.

  4. Вложенные асинхронные ожидания Не стоит использовать waitFor внутри других waitFor — это может привести к неопределённому поведению и увеличению времени теста.


Рекомендации по структуре теста

  • render компонента всегда должен быть первым шагом.
  • Все асинхронные действия (клики, вызовы API) должны происходить до waitFor.
  • Проверки состояния DOM — внутри waitFor.
  • Минимизировать использование setTimeout в тесте, предпочитая ожидание конкретных изменений DOM.

Использование waitFor обеспечивает надёжное тестирование асинхронных компонентов, гарантируя, что проверки DOM выполняются только после фактического обновления интерфейса. Это ключевой инструмент для создания стабильных и предсказуемых тестов в React Testing Library.