Тестирование в React обычно охватывает стандартные сценарии, когда компоненты работают так, как ожидается. Однако не менее важным является тестирование граничных случаев — ситуаций, которые могут происходить при необычных или крайних значениях входных данных. Эти тесты помогают выявить потенциальные ошибки, связанные с нестандартными условиями и поведением компонентов, что повышает устойчивость приложения.
Один из самых распространённых граничных случаев — это работа с
пустыми значениями. Это могут быть пустые строки, нулевые значения,
undefined, null. Тестирование на таких данных
позволяет убедиться, что компонент корректно обрабатывает отсутствие
данных и не вызывает ошибок.
Пример:
const InputField = ({ value }) => <input value={value} />;
Тест для проверки компонента, когда значение пустое:
import { render, screen } from '@testing-library/react';
import InputField from './InputField';
test('отображение пустого значения', () => {
render(<InputField value="" />);
const inputElement = screen.getByRole('textbox');
expect(inputElement.value).toBe('');
});
В данном примере проверяется, что компонент не выбрасывает ошибку, даже если передано пустое значение.
Необходимо убедиться, что компонент не ломается, если на вход
подаются null или undefined, особенно если
компонент зависит от этих значений для рендеринга.
Пример:
const Greeting = ({ name }) => <div>{name ? `Привет, ${name}` : 'Привет, Гость!'}</div>;
Тестирование с передачей null или
undefined:
test('пустое значение имени', () => {
render(<Greeting name={null} />);
const greetingText = screen.getByText('Привет, Гость!');
expect(greetingText).toBeInTheDocument();
});
В этом примере проверяется, что если имя не передано (или передано
как null), то будет отображаться дефолтное сообщение.
Когда компонент работает с числовыми значениями, важно проверять, как
он себя ведёт при минимальных, максимальных значениях и на краях
диапазона. Это особенно актуально для таких типов, как
number, где ожидаются конкретные границы.
Пример:
const Counter = ({ value }) => <div>{value}</div>;
Тестирование компонента на крайние значения:
test('минимальное значение счётчика', () => {
render(<Counter value={0} />);
const counterText = screen.getByText('0');
expect(counterText).toBeInTheDocument();
});
test('максимальное значение счётчика', () => {
render(<Counter value={1000000} />);
const counterText = screen.getByText('1000000');
expect(counterText).toBeInTheDocument();
});
Здесь тестируются минимальные и максимальные значения, которые может принять компонент.
Работа с пустыми или большими массивами — это типичный граничный случай. Необходимо убедиться, что компонент корректно рендерит пустой массив и правильно обрабатывает массивы с большим количеством элементов.
Пример:
const List = ({ items }) => (
<ul>
{items.length > 0 ? (
items.map((item, index) => <li key={index}>{item}</li>)
) : (
<li>Список пуст</li>
)}
</ul>
);
Тестирование с пустым массивом и большим количеством элементов:
test('пустой список', () => {
render(<List items={[]} />);
const listItem = screen.getByText('Список пуст');
expect(listItem).toBeInTheDocument();
});
test('список с элементами', () => {
render(<List items={['Элемент 1', 'Элемент 2', 'Элемент 3']} />);
const listItems = screen.getAllByRole('listitem');
expect(listItems).toHaveLength(3);
});
Эти тесты проверяют корректность рендеринга компонента при разных состояниях массива.
При тестировании асинхронных операций (например, получения данных с сервера) важно учитывать такие граничные случаи, как тайм-ауты, ошибки запросов или успешное завершение. React Testing Library предоставляет методы для асинхронных ожиданий.
Пример:
import { render, screen, waitFor } from '@testing-library/react';
import { fetchData } from './api';
import DataComponent from './DataComponent';
jest.mock('./api');
test('отображение данных после загрузки', async () => {
fetchData.mockResolvedValueOnce({ data: 'Привет, мир!' });
render(<DataComponent />);
const data = await waitFor(() => screen.getByText('Привет, мир!'));
expect(data).toBeInTheDocument();
});
test('ошибка при загрузке данных', async () => {
fetchData.mockRejectedValueOnce(new Error('Ошибка загрузки данных'));
render(<DataComponent />);
const errorMessage = await waitFor(() => screen.getByText('Ошибка загрузки данных'));
expect(errorMessage).toBeInTheDocument();
});
Здесь проверяется как обработка успешной загрузки, так и обработка ошибки.
Если компонент рендерит изображения или другие ресурсы, важно тестировать как он ведёт себя с большими, маленькими и нулевыми размерами этих ресурсов.
Пример:
const Avatar = ({ size }) => <img src="avatar.png" style={{ width: size, height: size }} alt="Аватар" />;
Тестирование с разными размерами:
test('маленький аватар', () => {
render(<Avatar size={30} />);
const avatar = screen.getByAltText('Аватар');
expect(avatar).toHaveStyle('width: 30px; height: 30px');
});
test('большой аватар', () => {
render(<Avatar size={200} />);
const avatar = screen.getByAltText('Аватар');
expect(avatar).toHaveStyle('width: 200px; height: 200px');
});
Эти тесты проверяют, как компонент управляет изменениями размеров.
При работе с состоянием компонента важно тестировать, как он реагирует на множественные рендеры и изменение состояния. Составление тестов, которые проверяют несколько последовательных изменений состояния, может помочь выявить ошибки, связанные с неправильной синхронизацией.
Пример:
const Toggle = () => {
const [isOn, setIsOn] = React.useState(false);
return <button onCl ick={() => setIsOn(!isOn)}>{isOn ? 'Включено' : 'Выключено'}</button>;
};
Тестирование переключения состояния:
test('переключение состояния', () => {
render(<Toggle />);
const button = screen.getByRole('button');
expect(button).toHaveTextContent('Выключено');
fireEvent.click(button);
expect(button).toHaveTextContent('Включено');
fireEvent.click(button);
expect(button).toHaveTextContent('Выключено');
});
Этот тест проверяет, как компонент меняет своё состояние при нескольких кликах.
Граничные случаи являются неотъемлемой частью процесса тестирования. Их правильная обработка позволяет убедиться, что компоненты React будут работать не только в стандартных ситуациях, но и при нестандартных входных данных или в экстремальных условиях. Тестирование на такие случаи делает приложение более стабильным и надежным, уменьшая вероятность неожиданных багов в продакшн-среде.