В React Testing Library взаимодействие с компонентами часто требует
работы с асинхронными процессами: сетевыми запросами, таймерами,
эффектами useEffect или любыми операциями, изменяющими
состояние компонента спустя некоторое время. Для корректного
тестирования таких сценариев используются утилиты waitFor и
act. Понимание их работы критично для написания надежных
тестов.
Асинхронные операции создают проблему гонок данных: тест может завершиться раньше, чем компонент успеет обновиться. Простая проверка на наличие элемента сразу после рендеринга в таких случаях часто проваливается. Для решения этой проблемы применяется ожидание, позволяющее тесту «дождаться» нужного состояния компонента.
React Testing Library предоставляет два ключевых механизма:
waitFor — ожидает, пока заданное
условие станет истинным.act — оборачивает код, изменяющий
состояние компонента, чтобы React корректно применял все обновления
перед проверкой результатов.waitFor:
ожидание изменения состоянияwaitFor позволяет повторно выполнять функцию-ассерцию до
тех пор, пока она не выполнится успешно или не истечет таймаут.
Синтаксис:
import { render, screen, waitFor } from '@testing-library/react';
await waitFor(() => {
expect(screen.getByText('Загрузка завершена')).toBeInTheDocument();
}, { timeout: 3000 });
Ключевые моменты:
timeout задаёт максимальное время ожидания. По
умолчанию 1000 мс.setTimeout.Пример с fetch-запросом:
import { render, screen, waitFor } from '@testing-library/react';
import UserList from './UserList';
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve([{ id: 1, name: 'Alice' }]),
})
);
test('отображает пользователей после загрузки', async () => {
render(<UserList />);
await waitFor(() => {
expect(screen.getByText('Alice')).toBeInTheDocument();
});
});
Здесь waitFor гарантирует, что проверка выполняется
только после того, как компонент получит данные и обновит DOM.
act: синхронизация с
ReactФункция act используется для обёртывания любых
действий, которые вызывают обновление состояния компонента,
включая асинхронные изменения. Это обеспечивает корректное обновление
DOM и предотвращает предупреждения о «необёрнутых действиях»
(act(...)).
Пример с синхронным обновлением состояния:
import { render, screen, fireEvent, act } from '@testing-library/react';
import Counter from './Counter';
test('увеличивает счетчик', () => {
render(<Counter />);
act(() => {
fireEvent.click(screen.getByText('Увеличить'));
});
expect(screen.getByText('1')).toBeInTheDocument();
});
Пример с асинхронным обновлением состояния:
import { render, screen, act } from '@testing-library/react';
import AsyncCounter from './AsyncCounter';
test('увеличивает счетчик через 500ms', async () => {
render(<AsyncCounter />);
await act(async () => {
screen.getByText('Увеличить').click();
await new Promise(resolve => setTimeout(resolve, 500));
});
expect(screen.getByText('1')).toBeInTheDocument();
});
Особенности act:
act должны завершаться
до выхода из него.await act(async () => {...}).act, будут завершены до проверки DOM.waitFor и actact — обёртывает обновление состояния,
уведомляя React о необходимости завершить все эффекты перед
проверкой.waitFor — повторяет ассерции, ожидая,
пока DOM не примет нужное состояние.Обычно асинхронные тесты React выглядят так:
fireEvent, вызов функции, рендер).act (для сложных
случаев, особенно с таймерами).waitFor, если DOM меняется
асинхронно.Пример комбинированного использования:
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
import AsyncToggle from './AsyncToggle';
test('переключает состояние асинхронно', async () => {
render(<AsyncToggle />);
await act(async () => {
fireEvent.click(screen.getByText('Переключить'));
});
await waitFor(() => {
expect(screen.getByText('Включено')).toBeInTheDocument();
});
});
Такой подход исключает состояния гонки и предупреждения React о «необработанных» обновлениях.
setTimeout для ожидания изменений без
waitFor — это ненадёжно и делает тесты медленными.act, даже если
кажется, что это не нужно.waitFor можно использовать с любыми селекторами DOM:
getByText, queryByRole,
findBy.... Последние (findBy) уже включают
внутреннее ожидание.act — проще делить на
логические блоки.waitFor повышает стабильность
тестов при медленных асинхронных операциях, но замедляет тестовую
прогонку.act для мелких синхронных действий
не критично, но для асинхронных операций помогает избежать
непредсказуемого поведения и предупреждений React.Асинхронное тестирование в React требует внимательного подхода к
состояниям и эффектам. Правильное использование waitFor и
act обеспечивает корректные, предсказуемые и
стабильные тесты, позволяя безопасно проверять как синхронные,
так и асинхронные изменения компонентов.