Приведи пример компонентного теста
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Пример компонентного теста на 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) — хотя можно добавить проверки на наличие классов.
Типичная структура компонентного теста:
- Setup: Рендер компонента с нужными props (
render(<Counter initialValue={5} />)). - Query: Поиск элементов в DOM для взаимодействия или проверки (
screen.getByTestId(...)). - Action (опционально): Выполнение пользовательского события (
fireEvent.click). - Assertion: Проверка ожидаемого результата (
expect(...).toHaveTextContent(...)).
Компонентные тесты являются фундаментом для пирамиды тестирования и обеспечивают быструю обратную связь при разработке и рефакторинге UI компонентов. Они должны быть быстрыми, стабильными и понятными, чтобы поддерживаться на протяжении всего жизненного цикла проекта.