В тестировании React-компонентов часто возникают сценарии, когда изменения интерфейса происходят не мгновенно, а в ответ на асинхронные события. Примеры включают запросы к серверу, задержку рендеринга данных, анимации или таймеры. React Testing Library (RTL) предоставляет инструменты для корректного тестирования таких случаев, позволяя писать надежные и поддерживаемые тесты.
1. Использование waitFor
Функция waitFor применяется для ожидания появления или
изменения элементов в DOM. Она периодически проверяет условие до того
момента, пока оно не станет истинным или не истечет таймаут.
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import AsyncButton from './AsyncButton';
test('кнопка обновляет текст после асинхронного действия', async () => {
render(<AsyncButton />);
userEvent.click(screen.getByText('Нажми меня'));
await waitFor(() => {
expect(screen.getByText('Загрузка завершена')).toBeInTheDocument();
});
});
Ключевые моменты:
waitFor возвращает промис, поэтому тест должен быть
асинхронным (async).{ timeout: 3000 }.2. Использование findBy
Методы с префиксом findBy (findByText,
findByRole и т.д.) объединяют поиск элемента и
ожидание его появления. Это сокращает необходимость оборачивать
проверку в waitFor.
test('отображает данные после загрузки', async () => {
render(<AsyncData />);
const item = await screen.findByText('Данные загружены');
expect(item).toBeInTheDocument();
});
Особенности:
findBy возвращает промис, который разрешается, когда
элемент найден.userEvent и задержки
Библиотека userEvent эмулирует действия пользователя,
включая клики, ввод текста и наведение курсора. В асинхронных сценариях
важно учитывать, что некоторые действия могут быть
асинхронными. Например, userEvent.type
возвращает промис, если используется с опцией задержки между
символами:
await userEvent.type(screen.getByRole('textbox'), 'Привет', { delay: 50 });
Когда пользователь инициирует действие, которое запускает асинхронное обновление состояния (например, запрос к API), правильное тестирование заключается в ожидании видимого результата, а не времени выполнения.
test('форма отправки данных отображает сообщение после ответа сервера', async () => {
render(<SubmitForm />);
userEvent.click(screen.getByText('Отправить'));
await screen.findByText('Данные успешно отправлены');
});
Для сложных компонентов, где одно действие пользователя запускает цепочку асинхронных изменений, рекомендуется чётко структурировать тест:
waitFor или
findBy.test('многошаговая асинхронная форма', async () => {
render(<MultiStepForm />);
userEvent.click(screen.getByText('Начать'));
// Ждём появления первого шага
await screen.findByText('Шаг 1');
userEvent.click(screen.getByText('Далее'));
await screen.findByText('Шаг 2');
userEvent.click(screen.getByText('Завершить'));
await screen.findByText('Форма успешно отправлена');
});
Иногда асинхронное действие реализовано через setTimeout
или setInterval. RTL совместим с Jest таймерами:
jest.useFakeTimers();
test('отображает уведомление через 2 секунды', () => {
render(<DelayedNotification />);
jest.advanceTimersByTime(2000);
expect(screen.getByText('Уведомление')).toBeInTheDocument();
});
jest.useRealTimers().Асинхронные тесты должны проверять обработку ошибок:
test('отображает сообщение об ошибке при неудачном запросе', async () => {
render(<AsyncData fail />);
await screen.findByText('Ошибка загрузки данных');
});
Это гарантирует, что UI корректно реагирует на сбои сервера или сети.
setTimeout в тестах для ожидания
состояния — лучше waitFor или findBy.userEvent, если
действия предполагают задержки.waitFor или findBy.Асинхронное тестирование в React Testing Library становится безопасным и стабильным, если ориентироваться на пользовательский интерфейс и видимые изменения, а не на внутреннюю реализацию. Такой подход делает тесты более читаемыми, устойчивыми к изменениям и приближенными к реальному поведению пользователей.