В React тестировании ключевым аспектом является корректная обработка
асинхронных обновлений состояния и эффектов
компонентов. Функция act играет центральную роль в
обеспечении того, чтобы все изменения состояния и побочные
эффекты были завершены до проверки ожидаемого результата.
actact — это функция, предоставляемая как React, так и
React Testing Library, которая гарантирует, что все обновления
состояния компонента обернуты и завершены до выполнения
проверок. Это критично для предотвращения предупреждений вида:
Warning: An update to ComponentName inside a test was not wrapped in act(...).
Без использования act тест может либо пройти
некорректно, либо стать нестабильным, так как обновления состояния в
React происходят асинхронно.
При синхронных изменениях состояния act можно
использовать следующим образом:
import { render, screen } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import Counter from './Counter';
test('увеличивает счетчик при клике', () => {
const { getByText } = render(<Counter />);
const button = getByText('Увеличить');
act(() => {
button.dispatchEvent(new MouseEvent('click', { bubbles: true }));
});
expect(getByText('Счетчик: 1')).toBeInTheDocument();
});
Здесь act оборачивает клик по кнопке, чтобы
обновление состояния счетчика завершилось до проверки
DOM.
Если компонент использует асинхронные операции, например
fetch или setTimeout, необходимо применять
асинхронную версию act:
import { render, screen } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import AsyncComponent from './AsyncComponent';
test('отображает данные после загрузки', async () => {
await act(async () => {
render(<AsyncComponent />);
});
expect(screen.getByText('Данные загружены')).toBeInTheDocument();
});
Асинхронная функция внутри act позволяет дождаться
всех промисов и эффектов useEffect, которые могли быть
вызваны при рендере компонента.
Для симуляции пользовательских действий, которые вызывают асинхронные
обновления, act также необходим:
import { fireEvent, render, screen } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import AsyncButton from './AsyncButton';
test('обрабатывает асинхронный клик', async () => {
render(<AsyncButton />);
const button = screen.getByText('Загрузить данные');
await act(async () => {
fireEvent.click(button);
});
expect(screen.getByText('Данные загружены')).toBeInTheDocument();
});
Здесь fireEvent.click помещается внутрь
act, чтобы все связанные асинхронные состояния
успели обновиться до проверки DOM.
act обязателенact.useEffect, который изменяет
состояние или взаимодействует с DOM.act без
необходимости. Оборачивание отдельных действий делает тесты более
читаемыми и локализует асинхронность.await act(async () => {}) для
асинхронных операций, чтобы избежать непредсказуемого поведения
теста.fireEvent и userEvent) уже оборачиваются в
act, поэтому явное использование необходимо только в
сложных сценариях.Функция act обеспечивает корректность тестирования
компонентов React за счёт гарантии того, что все обновления
состояния и эффекты завершены перед проверкой результатов.
Игнорирование act приводит к нестабильным тестам и
предупреждениям в консоли, особенно при асинхронных операциях. Важно
понимать разницу между синхронной и асинхронной версией
act и использовать её для точного управления
жизненным циклом компонентов в тестах.
Хотите, я составлю наглядную схему применения
act для разных сценариев в React Testing
Library?