Первый раз, когда мои RTL-тесты тормозили настолько, что я начал замечать лаг между fireEvent и ассертами, я списал это на «ну так и должно быть». Потом увидел, как 200-мс тесты превращаются в 3000-мс снежные комья в CI — и полез разбираться.
Откуда берётся тормознутость
Основной косяк RTL — его доброта. Под капотом происходит чудовищное количество работы:
- Рендер в JSDOM — хоть и легковесный, но каждый раз полный
- Дефолтные обертки —
React.StrictMode, контекстные провайдеры - Избыточные проверки — например,
cleanupпосле каждого теста, даже если DOM не менялся
Но главный пожиратель времени — waitFor. Каждый вызов добавляет 50-100 мс ожидания «на всякий случай», что убивает скорость в сценариях типа:
await waitFor(() => {
expect(screen.getByText('Done')).toBeInTheDocument();
});
// Даже если элемент появился через 1 мс,
// RTL будет ждать полный дефолтный таймаут
Практические оптимизации
1. Кастомизация рендера
Вместо стандартного render из RTL заводим свою фабрику с отключенным ненужным:
const fastRender = (ui, options) => {
return render(ui, {
...options,
wrapper: ({ children }) => (
<ThemeProvider>{children}</ThemeProvider> // Только нужные провайдеры
),
});
};
// В тестах используем только fastRender
test('should render', () => {
const { container } = fastRender(<Component />);
});
На практике это даёт +15-20% скорости сразу.
2. Агрессивные моки
RTL дружит с jest.mock, но мы можем пойти дальше:
jest.mock('heavy-module', () => ({
HeavyComponent: () => <div />, // Пустышка вместо реального компонента
}));
beforeAll(() => {
// Мокаем тяжелые API раз и навсегда
global.fetch = jest.fn(() => Promise.resolve({ json: () => ({}) }));
});
Важно: такие моки могут скрыть реальные баги, поэтому используем их только для:
- Сторонних библиотек
- Компонентов, которые уже покрыты юнит-тестами
- Медленных API-вызовов
3. Управление таймаутами
waitFor по умолчанию ждёт 1000 мс, но в 90% случаев нам хватит 100-200 мс:
const fastWaitFor = async (assertion) => {
await waitFor(assertion, { timeout: 200 });
};
test('should load data', async () => {
await fastWaitFor(() => {
expect(screen.getByText('Loaded')).toBeInTheDocument();
});
});
Дополнительно можно выставить в setupTests.js:
configure({
asyncUtilTimeout: 200, // Глобальный таймаут для всех async-утилит
});
Что не стоит оптимизировать
- Переход на shallow rendering — потеряем проверку реального взаимодействия
- Полный отказ от
waitFor— получим хрупкие тесты - Ручные
actвезде — RTL уже делает это под капотом
После всех оптимизаций мои тесты ускорились с ~12 до ~7 секунд на сюите из 50 тестов. Главный урок: RTL быстр, но требует настройки под конкретный кейс. Если ваши тесты тормозят — не спешите переписывать их на другую библиотеку, возможно, нужно просто убрать лишнее.
Источник: https://sigh.dev/posts/making-react-testing-library-faster/