Frontend-тесты ломаются чаще backend-овых — не потому, что фронт сложнее, а потому, что их часто пишут не про то. Тест, привязанный к классам, структуре разметки и внутреннему состоянию, краснеет на каждом рефакторинге, ничего не проверив по сути. Главный принцип frontend-тестирования один: проверять поведение, которое видит пользователь, а не то, как оно сделано внутри.

Инструменты

Раннер — Vitest (быстрый, совместим с экосистемой Vite, API как у Jest). Для компонентов — Testing Library: она рендерит компонент и даёт искать элементы и взаимодействовать с ними так, как это делает пользователь.

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

test("показывает товар и реагирует на открытие", async () => {
  const onOpen = vi.fn();
  render(<ProductCard id="1" name="Кофемолка" price={4990} onOpen={onOpen} />);

  expect(screen.getByText("Кофемолка")).toBeInTheDocument();
  await userEvent.click(screen.getByRole("button", { name: "Открыть" }));
  expect(onOpen).toHaveBeenCalledWith("1");
});

Запрос как пользователь

Ключевая идиома Testing Library — искать элементы по роли и доступному имени (getByRole, getByLabelText, getByText), а не по классам или test-id. Это сближает тест с тем, как воспринимает страницу пользователь и скринридер — и заодно проверяет доступность.

// хрупко: привязка к реализации
container.querySelector(".btn-primary");

// устойчиво: как ищет пользователь
screen.getByRole("button", { name: "Сохранить" });

Тест по роли переживает смену классов и перестройку разметки — он завязан на поведение, а не на устройство.

Что тестировать, а что нет

Тестировать стоит поведение: компонент показал нужное по данным, форма отдала введённое и показала ошибку, клик вызвал колбэк, экран отобразил состояние загрузки и ошибки. Не стоит тестировать реализацию: внутренний useState, имена классов, порядок вызовов хуков, точную верстку. Признак хорошего теста — он не меняется при рефакторинге, не меняющем поведение.

Серверные вызовы в тестах подменяют (мок fetch/слой API или MSW), чтобы тест был быстрым и не зависел от сети — так же, как backend подменяет порты.

Пирамида frontend-тестов

Та же форма, что в backend. Снизу, широко — юнит-тесты чистой логики (хуки, утилиты, валидация zod). Посередине — компонентные тесты на Testing Library (поведение компонента/экрана с моками данных) — основная масса. Сверху, узко — сквозные (e2e) тесты в реальном браузере; их мало, они медленные, и это отдельная специализация (Playwright), а не часть этого трека.

Не гонять логику через медленный e2e и не дублировать в каждом компонентном тесте полный поток — каждый уровень проверяет своё.

Где это в UCP

Тестирование frontend — это та же дисциплина, что в backend-биндингах: тестировать поведение и контракт, а не детали реализации; пирамида с быстрой широкой базой; подмена внешнего. Testing Library добавляет ценную связку — тест «как пользователь» заодно проверяет доступность. Сервис, который так покрыт, продукт-инженер меняет без страха, получая ответ за секунды, — и это замыкает фундамент frontend-специализации: от структуры проекта до качества.