Хотите заказать веб-сайт? Связаться с нами

Тестирование в React: Jest, RTL, Cypress

Тестирование — одна из тех практик, которые отличают код, готовый к продакшену, от кода, который просто работает на вашем компьютере.

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

Тестирование в React: Jest, RTL, Cypress

Зачем нужно тестирование и какие виды тестов существуют

Автоматические тесты решают сразу несколько задач. Во-первых, они ловят ошибки до того, как их найдут пользователи. Во-вторых, дают уверенность при рефакторинге: если тесты зелёные, значит поведение приложения не изменилось. В-третьих, тесты работают как живая документация — по ним видно, как компонент должен использоваться. Хорошо покрытый тестами модуль проще передать другому разработчику — не нужно бояться, что он случайно что-то сломает.

Три уровня тестов в экосистеме фронтенда

  1. Unit-тесты — проверяют отдельную функцию, хук или компонент в изоляции. Быстрые, дешёвые, их должно быть больше всего.
  2. Интеграционные тесты — проверяют, как несколько модулей работают вместе, например форма и её валидация.
  3. End-to-end (e2e) тесты — эмулируют реального пользователя в браузере: открывают страницу, кликают, заполняют поля. Медленные и дорогие в поддержке, но максимально приближены к реальности.

Для unit- и интеграционных тестов в React-мире стандартом стали Jest и React Testing Library, а для e2e — Cypress. Разберём каждый инструмент по отдельности.

Jest — фундамент модульного тестирования

Jest — это тест-раннер и библиотека утверждений в одном пакете.

Jest находит файлы с тестами, запускает их и показывает отчёт.

Если проект создан через Create React App или похожий шаблон, Jest уже настроен из коробки. В остальных случаях его можно установить вручную. Помимо React, Jest одинаково хорошо подходит для тестирования любого JavaScript-кода — от утилитных функций до серверных обработчиков.

npm install --save-dev jest

Структура теста

Тестовый файл обычно называют так же, как проверяемый модуль, с суффиксом .test.js. Внутри используются три ключевые функции: describe группирует связанные тесты, test (или его алиас it) описывает конкретную проверку, а expect формирует утверждение о результате.

function sum(a, b) {
  return a + b;
}
 
describe('sum', () => {
  test('складывает два положительных числа', () => {
    expect(sum(2, 3)).toBe(5);
  });
 
  test('возвращает NaN для нечисловых аргументов', () => {
    expect(sum('a', 3)).toBeNaN();
  });
});

У Jest есть десятки матчеров — toBe, toEqual, toContain, toThrow и другие, которые покрывают почти любой сценарий проверки: сравнение примитивов, глубокое сравнение объектов, проверку исключений.

Моки и шпионы

Часто нужно проверить не результат функции, а то, что она была вызвана с определёнными аргументами. Для этого Jest предоставляет jest.fn() — функцию-заглушку, которая запоминает все вызовы, и jest.mock() — для подмены целых модулей, например API-клиента, чтобы тест не делал реальных сетевых запросов.

const onClick = jest.fn();
button.click();
 
expect(onClick).toHaveBeenCalledTimes(1);

React Testing Library — тестируем компоненты, а не детали реализации

React Testing Library (RTL) построена вокруг одной идеи: тест должен взаимодействовать с компонентом так же, как это делает пользователь, а не заглядывать во внутреннее состояние или пропсы. Это делает тесты устойчивыми к рефакторингу — можно поменять реализацию компонента, и тест не сломается, пока поведение для пользователя осталось прежним.

Основные инструменты — функция render, которая монтирует компонент в виртуальный DOM, и объект screen, через который ищутся элементы на странице.

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

Для симуляции действий пользователя (клики, ввод текста, фокус) используется отдельный пакет @testing-library/user-event — он точнее эмулирует реальные события браузера, чем встроенный в Jest fireEvent.

Выбирайте запросы по приоритету, который рекомендует сама библиотека: сначала getByRole, затем getByLabelText и getByText, и только в крайнем случае getByTestId. Такой порядок заодно помогает находить проблемы с доступностью интерфейса.

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

Cypress — end-to-end тестирование в браузере

Cypress запускает тесты в настоящем браузере и проверяет приложение целиком: от открытия страницы до заполнения форм и переходов между маршрутами.

В отличие от Jest и RTL, которые работают с виртуальным DOM, Cypress видит именно то, что увидел бы пользователь.

npm install --save-dev cypress
npx cypress open

Тесты в Cypress пишутся в файлах с расширением .cy.js и используют цепочку команд: cy.visit открывает страницу, cy.get и cy.contains ищут элементы, cy.click и cy.type эмулируют действия, а should формирует утверждение.

describe('Форма входа', () => {
  it('пускает пользователя после ввода корректных данных', () => {
    cy.visit('/login');
    cy.get('[data-cy=email]').type('user@example.com');
    cy.get('[data-cy=password]').type('secret123');
    cy.get('[data-cy=submit]').click();
 
    cy.url().should('include', '/dashboard');
  });
});

Для запуска в CI без открытия окна браузера используется команда cypress run — она проходит все тесты в headless-режиме и возвращает код завершения, по которому можно понять, прошла ли сборка. Многие CI-системы умеют запускать эти тесты параллельно на нескольких машинах, что заметно ускоряет прогон объёмных наборов сценариев.

Не привязывайте селекторы Cypress к CSS-классам или структуре разметки — они меняются вместе с дизайном. Добавляйте отдельный атрибут вроде data-cy специально для тестов: он никак не влияет на стили и не сломается при рефакторинге вёрстки.

Как выбрать инструмент

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

Инструмент Когда использовать
Jest Проверка чистых функций, хуков и утилит без рендера DOM
React Testing Library Проверка поведения компонента с точки зрения пользователя
Cypress Проверка целых пользовательских сценариев в реальном браузере

Итоги

Мы разобрали три уровня тестирования React-приложений: быстрые unit-тесты на Jest, тесты компонентов через React Testing Library, которые проверяют поведение с точки зрения пользователя, и end-to-end сценарии в реальном браузере на Cypress. Вместе эти инструменты закрывают почти любой сценарий проверки — от отдельной функции до целого пользовательского пути.

В следующем уроке поговорим об управлении состоянием приложения — разберём, какие подходы к State Management существуют в React и когда каждый из них уместен.

Теги: