React Testing Library строится вокруг идеи тестирования поведения, а
не реализации. Однако на уровне тестового раннера остаётся важная
техническая деталь — жизненный цикл смонтированных
компонентов. Каждый вызов render создаёт новый
экземпляр дерева React, но без корректной очистки предыдущее состояние
может сохраниться в DOM и повлиять на последующие проверки.
Реидентификация в контексте тестирования — это ситуация, когда React или тестовый код ошибочно воспринимает старый экземпляр компонента как актуальный, либо когда тест обращается к DOM, содержащему остатки предыдущего рендера. Это особенно критично при:
document.bodycreatePortal)containerReact Testing Library решает эту проблему через централизованный механизм очистки (cleanup).
Cleanup — это процесс размонтирования всех React-компонентов, созданных в тесте, и удаления их DOM-узлов из документа.
Внутренне cleanup выполняет следующие действия:
unmount() для каждого дерева Reactdocument.bodyПример базового сценария:
import { render, screen } from '@testing-library/react';
test('отображается заголовок', () => {
render(<h1>Заголовок</h1>);
expect(screen.getByText('Заголовок')).toBeInTheDocument();
});
После завершения теста RTL автоматически выполнит cleanup, если среда настроена корректно.
Начиная с версии @testing-library/react 9+, cleanup
подключается автоматически через
afterEach, если используется Jest, Vitest или аналогичный
раннер с глобальным API.
Эквивалент внутренней логики:
afterEach(() => {
cleanup();
});
Это означает:
useEffect будут корректно
уничтоженыcomponentWillUnmount будет вызванРучное добавление cleanup в afterEach
требуется только в нестандартных окружениях или при отключённой
автоинициализации.
Существуют сценарии, где очистка может быть пропущена или недостаточна:
1. Кастомная среда тестирования
При использовании нестандартного раннера или отключённого
@testing-library/jest-dom автоматический cleanup может не
подключиться.
2. Ручное управление контейнером
const container = document.createElement('div');
document.body.appendChild(container);
render(<App />, { container });
Если контейнер создаётся вручную и не передаётся под контроль RTL, он не будет удалён автоматически.
3. Использование render вне тестов
Рендеринг компонентов в beforeAll или на уровне модуля
приводит к утечкам состояния:
const utils = render(<App />); // анти-паттерн
В этом случае cleanup не привязан к жизненному циклу теста.
В сложных тестах допустимо вызывать cleanup вручную:
import { render, cleanup } from '@testing-library/react';
afterEach(() => {
cleanup();
});
Это полезно, когда:
document.bodyВажно понимать, что cleanup() удаляет
все смонтированные деревья, а не только последнее.
RTL позволяет вызывать render несколько раз в рамках
одного теста. При этом повторный вызов:
const { rerender } = render(<Component a={1} />);
rerender(<Component a={2} />);
не создаёт новый экземпляр, а обновляет
существующий. Это контролируемая реидентификация — React сохраняет
состояние, хуки не пересоздаются, useEffect отрабатывает по
правилам зависимостей.
В отличие от этого, повторный render без
rerender:
render(<Component />);
render(<Component />);
создаёт два независимых дерева, оба будут удалены при cleanup.
Очистка напрямую влияет на корректность работы хуков.
При cleanup вызывается функция возврата эффекта:
useEffect(() => {
const id = setInterval(fn, 1000);
return () => clearInterval(id);
}, []);
Если cleanup не произошёл, таймер продолжит работать между тестами.
Эффекты синхронной очистки также корректно отрабатывают, но при ошибках cleanup возможны предупреждения о попытке обновления размонтированного компонента.
Если кастомный хук использует:
то отсутствие cleanup приведёт к накоплению состояния и неустойчивым тестам.
Асинхронные тесты требуют особого внимания. Cleanup запускается после завершения теста, но если внутри остаются незавершённые промисы или таймеры, возможны побочные эффекты.
Типичный источник проблем:
test('асинхронный запрос', async () => {
render(<Component />);
await screen.findByText('Готово');
});
Если компонент инициирует асинхронный эффект без корректной отмены, React может попытаться обновить состояние уже после cleanup, вызывая warning.
Корректный паттерн — отмена асинхронных операций внутри
useEffect:
useEffect(() => {
let active = true;
fetchData().then(data => {
if (active) setState(data);
});
return () => { active = false; };
}, []);
Компоненты, использующие createPortal, часто рендерятся
вне основного контейнера. RTL отслеживает порталы, созданные через
render, но при ручном управлении DOM возможны утечки:
const modalRoot = document.getElementById('modal-root');
Если modal-root не удаляется между тестами, контент
портала может сохраниться. Рекомендуется:
modal-root в beforeEachafterEachПризнаки отсутствия корректной очистки:
Практики диагностики:
screen.debug() перед и после тестаdocument.body.innerHTMLЧистые тесты опираются на жёсткую изоляцию:
Cleanup в React Testing Library — не вспомогательная утилита, а ключевой механизм, обеспечивающий корректную реидентификацию компонентов и надёжность всей тестовой системы.