waitFor после взаимодействия элементами

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


Синтаксис и базовое использование

import { render, screen, fireEvent, waitFor } from '@testing-library/react';

await waitFor(() => {
  // ожидание выполнения условия
});

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

  • waitFor принимает функцию, которая должна возвращать результат проверки.
  • Проверка повторяется каждые 50 мс по умолчанию, пока условие не выполнится или не истечёт таймаут (по умолчанию 1000 мс).
  • Если условие не выполнено за таймаут, тест падает с ошибкой.

Отличие от findBy и queryBy

  • findBy также ждёт появления элемента в DOM, но чаще используется для получения конкретного элемента.
  • queryBy и getBy выполняют синхронный поиск, не ждут изменений.

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


Использование после взаимодействий пользователя

Пример: форма с асинхронной отправкой данных.

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

test('отображает сообщение после успешной отправки', async () => {
  render(<Form />);

  userEvent.type(screen.getByLabelText(/имя/i), 'Иван');
  userEvent.click(screen.getByRole('button', { name: /отправить/i }));

  await waitFor(() => {
    expect(screen.getByText(/успешно отправлено/i)).toBeInTheDocument();
  });
});

Объяснение:

  1. Ввод данных и клик инициируют асинхронное событие.
  2. waitFor ждёт, пока DOM обновится с новым сообщением.
  3. Проверка внутри waitFor срабатывает только после появления элемента.

Частые ошибки при использовании waitFor

  1. Использование waitFor для синхронных действий Не нужно оборачивать проверки, которые происходят мгновенно:

    // Неправильно
    await waitFor(() => expect(screen.getByText(/текст/i)).toBeInTheDocument());
    // Если элемент уже в DOM, waitFor не нужен
  2. Слишком длинный код внутри waitFor Внутри функции лучше помещать только проверку, без лишней логики или мутаций.

  3. Ожидание элементов, которых нет waitFor упадёт с ошибкой после таймаута. Если элемент может отсутствовать, использовать waitFor с условием expect(...).not.toBeInTheDocument().


Настройка таймаута и интервала

waitFor принимает вторым аргументом объект настроек:

await waitFor(() => {
  expect(screen.getByText(/данные загружены/i)).toBeInTheDocument();
}, { timeout: 2000, interval: 100 });
  • timeout — максимальное время ожидания (в миллисекундах).
  • interval — частота повторной проверки условия.

Это полезно для тестирования медленных асинхронных операций, например, API-запросов или анимаций.


Комбинация с fireEvent и userEvent

  • fireEvent подходит для простых событий, таких как клик или изменение поля.
  • userEvent эмулирует более реалистичное взаимодействие пользователя (ввод текста, наведение, клики с задержкой).

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

userEvent.click(screen.getByText(/загрузить/i));

await waitFor(() => {
  expect(screen.getByRole('status')).toHaveTextContent('Загрузка завершена');
});

Проверка исчезновения элементов

waitFor также используется для ожидания исчезновения элементов:

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

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


Рекомендации по использованию

  • Оборачивать в waitFor только проверки, не создавая внутри побочных эффектов.
  • Для поиска одного элемента можно использовать findBy, для сложных условий — waitFor.
  • Избегать ненужного использования waitFor для синхронных операций.
  • Настраивать таймаут и интервал в зависимости от характера асинхронной операции.

Практический пример: список с асинхронной фильтрацией

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

test('фильтрация пользователей по имени', async () => {
  render(<UserList />);

  userEvent.type(screen.getByPlaceholderText(/поиск/i), 'Мария');

  await waitFor(() => {
    const items = screen.getAllByRole('listitem');
    expect(items).toHaveLength(1);
    expect(items[0]).toHaveTextContent('Мария');
  });
});

Здесь waitFor обеспечивает корректное ожидание обновления списка после ввода текста и асинхронной фильтрации.


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