Kodokon kodokon.com

Качество: Testing Library, StrictMode, Profiler

Тестируй поведение, а не реализацию, используй двойные вызовы StrictMode и измеряй рендеры через Profiler.

10 мин · 3 вопросов

Открыть этот урок в Kodokon

Философия Testing Library умещается в одну фразу: тестируй то, что воспринимает пользователь, и никогда - детали реализации. На практике это значит запрашивать DOM через дерево доступности: сначала getByRole (королева запросов, попутно проверяющая твои ARIA-роли), затем getByLabelText, getByText, и только в самом крайнем случае getByTestId. Для взаимодействий предпочитай user-event вместо fireEvent: там, где fireEvent.change порождает одно изолированное событие, user.type воспроизводит всю последовательность (focus, keydown, keypress, input, keyup), ровно как настоящая клавиатура. Варианты findBy* соединяют запрос с асинхронным ожиданием: незаменимо после шага загрузки.

JSX
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

test('adds a task to the list', async () => {
  const user = userEvent.setup();
  render(<TodoApp />);

  await user.type(
    screen.getByRole('textbox'),
    'Review the report'
  );
  await user.click(
    screen.getByRole('button', { name: /add/i })
  );

  expect(
    await screen.findByText('Review the report')
  ).toBeInTheDocument();
});
Тест, сосредоточенный на поведении, а не на реализации.

StrictMode - инструмент разработки, не имеющий ровно никакого эффекта в продакшене. В разработке он дважды вызывает функции твоих компонентов, инициализаторы состояния и функции, переданные в setState, чтобы вытащить наружу нечистые рендеры: если два выполнения дают разные результаты, у тебя есть скрытый побочный эффект. Начиная с React 18 он также прогоняет каждый эффект через цикл монтирование, размонтирование, повторное монтирование: эффект, чья очистка не идеально симметрична (подписка, которую не отменили, запрос, который не прервали), выдаёт себя сразу же. Это прямая подготовка к возможностям, где React размонтирует, а затем снова монтирует экран, сохраняя его состояние.

JSX
useEffect(() => {
  const controller = new AbortController();
  fetch('/api/user', { signal: controller.signal })
    .then((res) => res.json())
    .then(setUser)
    .catch(() => {});
  return () => controller.abort();
}, []);
Симметричный эффект переживает двойное монтирование StrictMode.

Для измерений React даёт компонент <Profiler> и его графический аналог в DevTools. Его коллбэк onRender получает, помимо прочего, phase (mount, update или nested-update), actualDuration (время, реально потраченное на рендеринг поддерева в этом коммите) и baseDuration (оценку времени рендеринга всего поддерева без какой-либо мемоизации). Разрыв между ними измеряет эффективность твоих memo и useMemo: actualDuration, сильно меньший baseDuration, означает, что мемоизация работает на тебя.

JSX
import { Profiler } from 'react';

function App() {
  return (
    <Profiler
      id="Sidebar"
      onRender={(id, phase, actualDuration) => {
        console.log(id, phase, actualDuration);
      }}
    >
      <Sidebar />
    </Profiler>
  );
}
Измерение реальной стоимости рендеринга поддерева.

Проверка знаний

Убедись, что запомнил ключевые моменты этого урока.

  1. Почему StrictMode в разработке монтирует, размонтирует, а потом снова монтирует каждый эффект?
    • Чтобы имитировать сетевые задержки сервера
    • Чтобы проверить, что очистка каждого эффекта симметрична
    • Чтобы очистить внутренний кэш React
    • Чтобы форсировать батчинг обновлений
  2. К какому запросу Testing Library стоит тянуться первым?
    • getByTestId, самому стабильному
    • getByClassName, самому точному
    • getByRole, согласованному с деревом доступности
    • querySelector, самому гибкому
  3. Что представляет собой baseDuration в коллбэке Profiler?
    • Время только последнего коммита
    • Оценочное время рендеринга поддерева без какой-либо мемоизации
    • Время, прошедшее с монтирования компонента
    • Среднюю длительность последних десяти рендеров