Урок по треку Инструменты 7 мин

Тесты: от unit до e2e

Пирамида тестов, unit, интеграционные и e2e, моки, Vitest, Testing Library и Playwright.

Зачем и какие тесты

Тест это код, который проверяет код и падает, если поведение сломалось. Главная ценность: смелость менять код без страха.

  • Unit: одна функция или модуль в изоляции. Быстрые (миллисекунды), их много.
  • Интеграционные: несколько частей вместе: компонент с хуками и стором, сервис с базой.
  • E2E (end-to-end): настоящий браузер кликает по собранному приложению. Медленные и дорогие, зато ловят то, что видит пользователь.

Пирамида тестов: много unit, меньше интеграционных, немного e2e. Во фронтенде популярен вариант «трофей»: упор на интеграционные тесты компонентов, потому что они ближе к реальному использованию и всё ещё быстрые.

Инструменты 2026

  • Vitest: раннер для проектов на Vite, API почти как у Jest (describe, it, expect, vi.fn). Jest по-прежнему живёт в старых проектах.
  • Testing Library: рендерит компонент и ищет элементы так, как это делает пользователь: по роли, тексту, label.
  • MSW (Mock Service Worker): перехват сетевых запросов на уровне сети, а не подмена fetch в каждом тесте.
  • Playwright: e2e в Chromium, Firefox и WebKit, с автоожиданием элементов.

Структура теста: Arrange, Act, Assert

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

it('увеличивает счётчик по клику', async () => {
  render(<Counter />);                                       // Arrange
  await userEvent.click(screen.getByRole('button', { name: '+' })); // Act
  expect(screen.getByText('Счёт: 1')).toBeInTheDocument();   // Assert
});

Тестируй поведение, а не реализацию: что видит и делает пользователь, а не внутренний state или имена приватных методов. Тогда рефакторинг не ломает тесты.

Запросы Testing Library

МетодНе нашёлКогда
getBy...бросает ошибкуэлемент должен быть сейчас
queryBy...возвращает nullпроверить, что элемента нет
findBy...промис, ждёт появления (по умолчанию до 1 с)элемент появится асинхронно

Приоритет: getByRole и getByLabelText лучше, чем getByTestId, потому что заодно проверяют доступность.

Моки и тестовые двойники

  • stub: подставная реализация с заранее заданным ответом;
  • spy: обёртка, которая запоминает вызовы (с какими аргументами, сколько раз);
  • mock: двойник с ожиданиями, которые проверяются в тесте.
const onSave = vi.fn();
render(<Form onSave={onSave} />);
// ...
expect(onSave).toHaveBeenCalledWith({ name: 'Аня' });

vi.mock('./api') подменяет модуль целиком, вызов поднимается (hoisting) в начало файла до импортов. Мокать стоит границы системы (сеть, время, случайность), а не всё подряд: тест, где замокано всё, проверяет моки.

Время подделывают через vi.useFakeTimers(), чтобы тестировать debounce и таймауты без реального ожидания.

Покрытие

Coverage показывает, какие строки выполнялись в тестах, но не то, что их результат проверен. 100% покрытия без хороших expect ничего не гарантирует. Это ориентир, а не цель.

Флаки-тесты

Тест, который то проходит, то падает без изменений кода. Причины: гонки и фиксированные sleep, зависимость от порядка тестов и общего состояния, реальное время и сеть. Лечится ожиданием условия (findBy, автоожидание Playwright), изоляцией данных и фейковыми таймерами, а не увеличением таймаутов.

TDD

Цикл red, green, refactor: написать падающий тест, минимальный код до зелёного, затем улучшить код под защитой теста.

Частые ошибки

  • Тестировать реализацию: state, вызовы внутренних функций.
  • getBy для проверки отсутствия элемента (бросит ошибку раньше expect).
  • setTimeout/sleep в тестах вместо ожидания условия.
  • Гнаться за процентом покрытия.

Что спросят на собесе

  • Пирамида тестов и чем unit отличается от интеграционного и e2e.
  • Что такое моки, стабы и шпионы, что стоит мокать.
  • Как тестировать React-компонент и почему по ролям.
  • Откуда берутся флаки-тесты и как их лечить.
  • Что даёт и чего не даёт coverage.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.