Как тестировать React-компоненты с помощью Jest и React Testing Library?

Ответ

Для unit-тестирования React-компонентов используется связка Jest (тест-раннер) и React Testing Library (RTL) (утилиты для взаимодействия с компонентами).

Ключевая философия RTL — тестировать компоненты так, как их видит пользователь, фокусируясь на поведении, а не на деталях реализации.

Основные виды тестов:

  1. Тестирование рендеринга Проверяем, что компонент корректно отображается с нужным контентом.

    import { render, screen } from '@testing-library/react';
    import Button from './Button';
    
    test('renders button with correct text', () => {
      render(<Button>Click Me</Button>);
      // Ищем элемент по его роли и тексту, как это сделал бы пользователь
      expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument();
    });
  2. Тестирование событий Имитируем действия пользователя (клик, ввод текста) и проверяем реакцию компонента.

    import { render, screen, fireEvent } from '@testing-library/react';
    
    test('calls onClick handler when clicked', () => {
      const handleClick = jest.fn(); // Создаем mock-функцию
      render(<Button onClick={handleClick}>Click Me</Button>);
    
      fireEvent.click(screen.getByText(/click me/i));
    
      // Проверяем, что обработчик был вызван один раз
      expect(handleClick).toHaveBeenCalledTimes(1);
    });
  3. Тестирование асинхронных операций Для компонентов, выполняющих API-запросы, используются async/await и методы findBy* для ожидания появления элементов.

    test('loads and displays user data', async () => {
      render(<UserComponent />);
      // findBy* возвращает Promise, который разрешится, когда элемент появится в DOM
      const userName = await screen.findByText('John Doe');
      expect(userName).toBeInTheDocument();
    });

    Для изоляции тестов от реального бэкенда API-запросы мокаются с помощью jest.mock или библиотек вроде Mock Service Worker (MSW).

Ответ 18+ 🔞

Слушай, а вот эта вся тема с тестированием компонентов в Реакте — это вообще отдельный вид искусства, блядь. Люди думают: «О, накидал кнопок, всё работает, зачем тесты?». А потом приходит новый чувак, чихнет рядом с кодом, и всё разъёбывается к хуям собачьим. Так вот, чтобы такого не было, есть два главных героя: Jest и React Testing Library (RWT, блядь, RTL).

Фишка RTL в том, что ты не ковыряешься во внутренностях компонента, как какой-то хирург-извращенец. Ты тестируешь его так, как видит пользователь: что на экране появилось, куда можно тыкнуть, что после этого произойдёт. Всё, пиздец, просто и гениально.

На чём обычно ловят воробушков, то есть пишут тесты:

  1. Просто рендеринг, ёпта Проверяем, что компонент вообще появился на свет божий и показывает то, что должен.

    import { render, screen } from '@testing-library/react';
    import Button from './Button';
    
    test('renders button with correct text', () => {
      render(<Button>Click Me</Button>);
      // Ищем кнопку по её роли и тексту, как это сделал бы нормальный человек, а не робот
      expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument();
    });

    Если тут тест упадёт — значит, ты или кнопку криво написал, или уже совсем еблан.

  2. Тестирование кликов и прочей активности Вот тут начинается магия. Нужно сымитировать, что пользователь что-то сделал, и проверить, отреагировал ли компонент.

    import { render, screen, fireEvent } from '@testing-library/react';
    
    test('calls onClick handler when clicked', () => {
      const handleClick = jest.fn(); // Создаём муляж функции, шпиона, короче
      render(<Button onClick={handleClick}>Click Me</Button>);
    
      // Симулируем клик, как будто палец пользователя дрогнул
      fireEvent.click(screen.getByText(/click me/i));
    
      // А теперь проверяем, вызвалась ли наша функция-шпион ровно один раз
      expect(handleClick).toHaveBeenCalledTimes(1);
    });

    Если handleClick не вызвался — значит, твоя кнопка просто красивая картинка, и её надо выкинуть в помойку.

  3. Асинхронщина, блядь, самая большая головная боль Когда компонент лезет за данными на сервер, а мы не хотим, чтобы тесты реально туда стучались. Иначе один тест будет работать, а потом сервер ляжет, и все твои тесты накроются медным тазом.

    test('loads and displays user data', async () => {
      render(<UserComponent />);
      // Метод findBy* — он умный, ждёт, пока элемент появится. Как будто сидишь и пялишься в экран.
      const userName = await screen.findByText('John Doe');
      expect(userName).toBeInTheDocument();
    });

    А чтобы не дёргать реальный бэкенд, запросы мокают. Либо через jest.mock, либо через специальные штуки вроде Mock Service Worker (MSW), которые перехватывают твои запросы и подсовывают фейковые ответы. Красота, в рот меня чих-пых!

Вот и вся наука. Не так страшен чёрт, как его малюют. Главное — начать, а потом уже сам от себя офигеешь, насколько проще жить, когда за спиной стоит армия тестов, которая не даёт тебе накосячить.