← Назад к вопросам

Приведи пример компонентного теста

2.3 Middle🔥 131 комментариев
#Теория тестирования

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Пример компонентного теста на React с использованием Jest и React Testing Library

Компонентный тест (Component Test) — это тест, который проверяет отдельный компонент пользовательского интерфейса в изоляции от других частей системы. Он фокусируется на внутренней логике, отображении, пользовательских событиях и взаимодействии с props/state. Ниже приведён пример для современного React-компонента.

Компонент Counter.tsx

Представим простой компонент счетчика с кнопками увеличения и уменьшения значения.

import React, { useState } from 'react';

interface CounterProps {
  initialValue?: number;
}

export const Counter: React.FC<CounterProps> = ({ initialValue = 0 }) => {
  const [count, setCount] = useState<number>(initialValue);

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);
  const reset = () => setCount(initialValue);

  return (
    <div data-testid="counter-component">
      <h2>Counter: {count}</h2>
      <button onClick={increment} data-testid="increment-btn">Increment</button>
      <button onClick={decrement} data-testid="decrement-btn">Decrement</button>
      <button onClick={reset} data-testid="reset-btn">Reset</button>
      <p>Initial value was: {initialValue}</p>
    </div>
  );
};

Тестовый файл Counter.test.tsx

Тест написан с использованием Jest и React Testing Library (RTL), что является стандартом для компонентных тестов в React.

import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import { Counter } from './Counter';

describe('Counter Component', () => {
  // Тест на правильное отображение начального значения
  test('renders with initial value', () => {
    render(<Counter initialValue={5} />);
    
    // Проверяем, что элемент с текстом "Counter: 5" присутствует
    expect(screen.getByText('Counter: 5')).toBeInTheDocument();
    // Проверяем сообщение о начальном значении
    expect(screen.getByText('Initial value was: 5')).toBeInTheDocument();
  });

  // Тест на увеличение счетчика
  test('increments count when increment button is clicked', () => {
    render(<Counter />);
    const incrementButton = screen.getByTestId('increment-btn');
    const counterDisplay = screen.getByText(/Counter:/); // Регулярное выражение для части текста

    // Изначально 0
    expect(counterDisplay).toHaveTextContent('Counter: 0');
    
    fireEvent.click(incrementButton);
    expect(counterDisplay).toHaveTextContent('Counter: 1');
    
    fireEvent.click(incrementButton);
    expect(counterDisplay).toHaveTextContent('Counter: 2');
  });

  // Тест на уменьшение счетчика
  test('decrements count when decrement button is clicked', () => {
    render(<Counter initialValue={10} />);
    const decrementButton = screen.getByTestId('decrement-btn');
    const counterDisplay = screen.getByText(/Counter:/);

    expect(counterDisplay).toHaveTextContent('Counter: 10');
    
    fireEvent.click(decrementButton);
    expect(counterDisplay).toHaveTextContent('Counter: 9');
  });

  // Тест на сброс счетчика к начальному значению
  test('resets count to initial value when reset button is clicked', () => {
    render(<Counter initialValue={3} />);
    const incrementButton = screen.getByTestId('increment-btn');
    const resetButton = screen.getByTestId('reset-btn');
    const counterDisplay = screen.getByText(/Counter:/);

    // Увеличиваем два раза
    fireEvent.click(incrementButton);
    fireEvent.click(incrementButton);
    expect(counterDisplay).toHaveTextContent('Counter: 5');

    // Сбрасываем
    fireEvent.click(resetButton);
    expect(counterDisplay).toHaveTextContent('Counter: 3');
  });

  // Тест на отображение при отсутствии пропса initialValue (дефолтное значение)
  test('renders with default initial value (0) when no prop is provided', () => {
    render(<Counter />);
    expect(screen.getByText('Counter: 0')).toBeInTheDocument();
    expect(screen.getByText('Initial value was: 0')).toBeInTheDocument();
  });
});

Ключевые аспекты компонентного теста в примере:

  • Изоляция: Тестируется только компонент Counter, без зависимостей от API, родителей, контекста или глобального состояния (например, Redux). Это позволяет быстро локализовать ошибки.
  • Функциональное покрытие: Тесты проверяют все основные функции:
    * Инициализация состояния (включая дефолтные значения).
    * Реакция на пользовательские события (`click`).
    * Правильное изменение внутреннего состояния (`count`).
    * Корректное отображение UI в зависимости от состояния.
  • Использование data-testid: Атрибуты data-testid позволяют надежно находить элементы в тестах, даже если их текст или структура изменятся.
  • Тестирование через пользовательские действия: RTL рекомендует тестировать компоненты так, как это делает пользователь — через взаимодействие с интерфейсом (fireEvent), а не через прямые манипуляции с состоянием.
  • Проверка DOM: Вместо проверки внутреннего состояния компонента (count), тесты проверяют результат его работы — текст на экране. Это более устойчивый подход.

Что мы не тестируем в этом компонентном тесте (границы):

  • Интеграцию с другими компонентами (например, если Counter часть более крупной формы).
  • Запросы к API (в данном компоненте их нет).
  • Работу с глобальным состоянием (Redux, MobX).
  • Стили (CSS) — хотя можно добавить проверки на наличие классов.

Типичная структура компонентного теста:

  1. Setup: Рендер компонента с нужными props (render(<Counter initialValue={5} />)).
  2. Query: Поиск элементов в DOM для взаимодействия или проверки (screen.getByTestId(...)).
  3. Action (опционально): Выполнение пользовательского события (fireEvent.click).
  4. Assertion: Проверка ожидаемого результата (expect(...).toHaveTextContent(...)).

Компонентные тесты являются фундаментом для пирамиды тестирования и обеспечивают быструю обратную связь при разработке и рефакторинге UI компонентов. Они должны быть быстрыми, стабильными и понятными, чтобы поддерживаться на протяжении всего жизненного цикла проекта.