PurpleSchool — курсы программирования онлайн
  • Пути
    • Frontend React разработчик
    • Frontend Vue разработчик
    • Backend разработчик Node.js
    • Fullstack разработчик React / Node.js
    • Mobile разработчик React Native
    • Backend разработчик Golang
    • Devops инженер
    • Backend разработчик Python
  • AI для кодаНовое
  • О нас
    • Отзывы
    • Реферальная программа
    • О компании
    • Контакты
  • Иконка открытия меню
    • Сообщество
    • PurpleПлюс
    • AI Собеседование
    • AI тренажёр
    • Проекты
PurpleSchool — платформа бесплатных roadmap и курсов для разработчиков
ютуб иконка
Telegram иконка
VK иконка
VK иконка
Курсы
ГлавнаяКаталог курсовFrontendBackendFullstack
Практика
КарьераПроектыPurpleПлюс
Материалы
БлогБаза знаний
Документы
Договор офертаПолитика конфиденциальностиПроверка сертификатаМиграция курсовРеферальная программа
Реквизиты
ИП Ларичев Антон АндреевичИНН 773373765379contact@purpleschool.ru

PurpleSchool © 2020 -2026 Все права защищены

  • Курсы
    • FrontendИконка стрелки
    • AI разработкаИконка стрелки
    • BackendИконка стрелки
    • DevOpsИконка стрелки
    • MobileИконка стрелки
    • ТестированиеИконка стрелки
    • Soft-skillsИконка стрелки
    • ДизайнИконка стрелки
    Иконка слояПерейти в каталог курсов
  • Бесплатно
    • Курсы
    • JavaScript Основы разработкиPython Основы PythonCSS CSS FlexboxКарта развитияВопросы для собеседований
    • База знанийИконка стрелки
    • Новостные рассылкиИконка стрелки
  • PurpleSchool — курсы программирования онлайн
    • AI для кодаНовое
    • Сообщество
    • PurpleПлюс
    • AI Собеседование
    • AI тренажёр
    • Проекты
    Главная
    Сообщество
    Тестирование React-компонентов с Jest и React Testing Library

    Тестирование React-компонентов с Jest и React Testing Library

    Аватар автора Тестирование React-компонентов с Jest и React Testing Library

    Антон Ларичев

    Иконка календаря07 октября 2026
    JestReact Testing LibraryТестированиеReactmiddleИконка уровня middle
    Картинка поста Тестирование React-компонентов с Jest и React Testing Library

    Введение

    Тестирование React-компонентов — это не просто модная практика, а способ сэкономить часы на ручной проверке UI после каждого изменения. Связка Jest и React Testing Library (RTL) стала стандартом в экосистеме React: Jest берёт на себя запуск тестов, моки и ассерты, а RTL предлагает API, которое заставляет писать тесты так, как реальный пользователь взаимодействует с интерфейсом — через клики, ввод текста и поиск элементов по доступным ролям, а не по внутренним деталям реализации.

    В этой статье разберём, как настроить тестовое окружение, написать первые тесты компонентов, протестировать пользовательские события, асинхронные запросы, мокировать зависимости и проверить кастомные хуки.

    Установка и настройка

    Если проект создан через Create React App, Jest и RTL уже встроены. Для Vite или кастомной сборки на Webpack нужно установить зависимости вручную:

    npm install --save-dev jest @testing-library/react @testing-library/jest-dom @testing-library/user-event jest-environment-jsdom
    

    Базовый конфиг jest.config.js:

    // jest.config.js
    module.exports = {
      testEnvironment: 'jsdom', // имитация браузерного окружения
      setupFilesAfterEach: ['@testing-library/jest-dom'],
      moduleNameMapper: {
        '\\.(css|less|scss)$': 'identity-obj-proxy', // игнорируем стили
      },
    };
    

    Файл настройки для подключения дополнительных матчеров:

    // jest.setup.js
    import '@testing-library/jest-dom';
    

    Первый тест компонента

    Допустим, есть простой компонент счётчика:

    // Counter.jsx
    import { useState } from 'react';
    
    export function Counter() {
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <p data-testid="count">Счёт: {count}</p>
          <button onClick={() => setCount(count + 1)}>Увеличить</button>
        </div>
      );
    }
    

    Тест для него:

    // Counter.test.jsx
    import { render, screen } from '@testing-library/react';
    import { Counter } from './Counter';
    
    test('отображает начальное значение счётчика', () => {
      render(<Counter />);
      // ищем элемент по тексту, как это делал бы пользователь
      expect(screen.getByText('Счёт: 0')).toBeInTheDocument();
    });
    

    Главный принцип RTL — искать элементы так, как их видит пользователь: по тексту, роли или подписи, а не по CSS-классам.

    Работа с пользовательскими событиями

    Для имитации кликов и ввода текста используется библиотека user-event, которая точнее воспроизводит реальное поведение браузера, чем встроенный fireEvent:

    import { render, screen } from '@testing-library/react';
    import userEvent from '@testing-library/user-event';
    import { Counter } from './Counter';
    
    test('увеличивает счётчик при клике', async () => {
      const user = userEvent.setup();
      render(<Counter />);
    
      const button = screen.getByRole('button', { name: 'Увеличить' });
      await user.click(button);
    
      expect(screen.getByText('Счёт: 1')).toBeInTheDocument();
    });
    

    Тестирование асинхронного кода

    Компоненты часто загружают данные после монтирования. Для ожидания появления элемента после асинхронной операции используются асинхронные варианты запросов:

    // UserProfile.jsx
    import { useEffect, useState } from 'react';
    
    export function UserProfile({ userId }) {
      const [name, setName] = useState(null);
    
      useEffect(() => {
        fetch(`/api/users/${userId}`)
          .then((res) => res.json())
          .then((data) => setName(data.name));
      }, [userId]);
    
      if (!name) return <p>Загрузка...</p>;
      return <p>Имя: {name}</p>;
    }
    
    import { render, screen } from '@testing-library/react';
    import { UserProfile } from './UserProfile';
    
    test('показывает имя после загрузки', async () => {
      // мокируем глобальный fetch
      global.fetch = jest.fn(() =>
        Promise.resolve({
          json: () => Promise.resolve({ name: 'Анна' }),
        })
      );
    
      render(<UserProfile userId={1} />);
    
      // findBy автоматически ждёт появления элемента
      const nameElement = await screen.findByText('Имя: Анна');
      expect(nameElement).toBeInTheDocument();
    });
    

    Мокирование зависимостей

    Jest позволяет подменять модули целиком, что удобно для изоляции компонента от внешних сервисов:

    // api.js
    export async function fetchUser(id) {
      const res = await fetch(`/api/users/${id}`);
      return res.json();
    }
    
    import { render, screen } from '@testing-library/react';
    import { UserProfile } from './UserProfile';
    import { fetchUser } from './api';
    
    jest.mock('./api');
    
    test('отображает имя пользователя из мока', async () => {
      fetchUser.mockResolvedValue({ name: 'Иван' });
    
      render(<UserProfile userId={2} />);
    
      expect(await screen.findByText('Имя: Иван')).toBeInTheDocument();
    });
    

    Тестирование кастомных хуков

    Для изолированного тестирования хуков используется renderHook из @testing-library/react:

    // useCounter.js
    import { useState, useCallback } from 'react';
    
    export function useCounter(initial = 0) {
      const [count, setCount] = useState(initial);
      const increment = useCallback(() => setCount((c) => c + 1), []);
      return { count, increment };
    }
    
    import { renderHook, act } from '@testing-library/react';
    import { useCounter } from './useCounter';
    
    test('увеличивает значение при вызове increment', () => {
      const { result } = renderHook(() => useCounter(5));
    
      act(() => {
        result.current.increment();
      });
    
      expect(result.current.count).toBe(6);
    });
    

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

    Поиск элементов по CSS-классам или id вместо роли и текста. Такой тест ломается при любом рефакторинге разметки, хотя поведение компонента не изменилось.

    Использование fireEvent там, где нужен userEvent. fireEvent не вызывает побочные эффекты браузера, например фокус, из-за чего тесты пропускают реальные баги.

    Забытый await перед асинхронными действиями user-event и findBy-запросами. Без await тест может завершиться раньше, чем обновится DOM, и дать ложноположительный результат.

    Чрезмерное мокирование. Если замокать слишком много зависимостей, тест начинает проверять не поведение компонента, а корректность самих моков.

    Тестирование деталей реализации вместо результата. Проверка внутреннего состояния через instance-методы или приватные переменные делает тесты хрупкими и бесполезными при рефакторинге.

    Отсутствие очистки между тестами. Если не сбрасывать моки и таймеры после каждого теста, результаты одного теста могут влиять на следующий.

    Заключение

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

    Иконка глаза5

    Комментарии

    0

    Постройте личный план изучения JavaScript Advanced - продвинутые концепции языка и ООП до уровня Middle — бесплатно!

    JavaScript Advanced - продвинутые концепции языка и ООП — часть карты развития Frontend, Backend, Mobile

    • step100+ шагов развития
    • lessons30 бесплатных лекций
    • lessons300 бонусных рублей на счет

    Бесплатные лекции

    Лучшие курсы по теме

    изображение курса

    TypeScript с нуля

    Антон Ларичев
    AI-тренажерыAI-тренажеры
    Практика в студииПрактика в студии
    Гарантия
    Бонусы
    иконка звёздочки рейтинга4.8
    3 999 ₽ 6 990 ₽
    Подробнее
    изображение курса

    React и Redux Toolkit

    Антон Ларичев
    AI-тренажерыAI-тренажеры
    Практика в студииПрактика в студии
    Гарантия
    Бонусы
    иконка звёздочки рейтинга4.8
    3 999 ₽ 6 990 ₽
    Подробнее
    изображение курса

    Neovim

    Антон Ларичев
    Гарантия
    Бонусы
    иконка звёздочки рейтинга4.8
    3 999 ₽ 6 990 ₽
    Подробнее

    Похожие статьи

    Иконка аватараАнтон
    Иконка календаря07 октября 2026
    Claude CodeAI-инструментыDevOps+ 2middleИконка уровня middle

    Roo Code в VS Code: подключение Claude из России

    Roo Code начинает работать из России после одной правки в настройках: провайдер — Anthropic, в поле ключа — ваш API-ключ, в поле адреса — прокси, который отвечает без VPN и берёт оплату рублями. Само расширение ставится из Marketplace обычным порядком и остаётся нетронутым: ни форка, ни патча, ни туннеля. Ключ и адрес выдаёт [AI для кода](https://purpleschool.ru/ai-for-code). Ниже — установка, настройка провайдера, режимы Roo Code и ошибки, на которых спотыкается первое подключение.

    Иконка чипа0
    Иконка глаза11
    Иконка комментариев0
    Иконка аватараАнтон
    Иконка календаря06 октября 2026
    Claude CodeAI-инструментыDevOps+ 2middleИконка уровня middle

    Claude Code или Cursor: что выбрать в 2026 году

    Короткий ответ: Claude Code берут, когда агент должен работать сам — в терминале, по инструкциям из репозитория, без открытого редактора; Cursor — когда нужен редактор, в котором ассистент всегда под

    Иконка чипа0
    Иконка глаза92
    Иконка комментариев0
    Иконка аватараАнтон
    Иконка календаря01 октября 2026
    AI-инструментыDevOpsразработка+ 2middleИконка уровня middle

    Gemini CLI в России: как установить и настроить

    Установить Gemini CLI из России можно без VPN и без ухищрений — это обычный npm-пакет, он скачивается и запускается на любой машине. Упирается всё на следующем шаге: 18 июня 2026 года Google перестал обслуживать запросы Gemini CLI для бесплатных аккаунтов и подписчиков Google AI Pro и Ultra, а единственный оставшийся путь — вход по API-ключу — требует биллинга в регионе, где Россия не числится. Ниже разбираем, как агент ставится, как устроены три способа входа, почему ни один из них не доводи...

    Иконка чипа0
    Иконка глаза471
    Иконка комментариев0
    Иконка чипа0