Положительные и отрицательные случаи

Одной из важнейших задач при тестировании компонентов с использованием React Testing Library (RTL) является грамотное покрытие как положительных, так и отрицательных сценариев. Понимание того, как правильно создавать и запускать такие тесты, позволяет удостовериться в корректной работе приложения в различных условиях.

Положительные случаи

Положительные тесты (или тесты “happy path”) предназначены для проверки того, что приложение или компонент работает правильно, когда все условия соблюдены и пользователь взаимодействует с системой в рамках предполагаемого сценария.

При создании положительных тестов важно сосредоточиться на функциональности, которая должна успешно работать. Например, если у нас есть форма с полями ввода и кнопкой отправки, положительный тест может проверить, что при правильном вводе данных форма отправляется корректно и показывается успешное сообщение.

Пример теста для формы
import { render, screen, fireEvent } from '@testing-library/react';
import MyForm from './MyForm';

test('форма отправляется при правильном вводе данных', () => {
  render(<MyForm />);
  
  // Поиск элементов
  const inputName = screen.getByLabelText(/имя/i);
  const inputEmail = screen.getByLabelText(/email/i);
  const submitButton = screen.getByRole('button', { name: /отправить/i });

  // Ввод данных
  fireEvent.change(inputName, { target: { value: 'Иван' } });
  fireEvent.change(inputEmail, { target: { value: 'ivan@example.com' } });

  // Отправка формы
  fireEvent.click(submitButton);

  // Проверка результата
  const successMessage = screen.getByText(/форма успешно отправлена/i);
  expect(successMessage).toBeInTheDocument();
});

В этом примере тест проверяет, что при вводе корректных данных и отправке формы, появляется сообщение об успешной отправке. Это классический положительный тест, который следует ожидать в случае правильной работы компонента.

Важные моменты
  • Поиск элементов: Для поиска элементов следует использовать доступные роли, метки, заголовки и текстовые значения. RTL акцентирует внимание на доступности и взаимодействии пользователя с компонентами.
  • Изменение состояний: Для проверки поведения компонента при изменении данных используются события, такие как fireEvent.change или user-event.type.
  • Результат: После выполнения действия проверяется наличие ожидаемого результата, будь то изменение текста, состояния или появления нового элемента на странице.

Отрицательные случаи

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

Пример теста для некорректной формы
import { render, screen, fireEvent } from '@testing-library/react';
import MyForm from './MyForm';

test('показывает ошибку при неверном email', () => {
  render(<MyForm />);
  
  // Поиск элементов
  const inputEmail = screen.getByLabelText(/email/i);
  const submitButton = screen.getByRole('button', { name: /отправить/i });

  // Ввод некорректных данных
  fireEvent.change(inputEmail, { target: { value: 'invalid-email' } });

  // Отправка формы
  fireEvent.click(submitButton);

  // Проверка ошибки
  const errorMessage = screen.getByText(/некорректный email/i);
  expect(errorMessage).toBeInTheDocument();
});

В этом тесте проверяется, что при вводе некорректного email пользователю показывается сообщение об ошибке. Такие тесты важны для обеспечения надежности компонента и предотвращения проблем в реальных условиях эксплуатации.

Важные моменты
  • Обработка ошибок: Нужно предусмотреть все возможные варианты ошибок, включая неверный формат данных, отсутствие обязательных полей или несанкционированные действия.
  • Пользовательский ввод: Отрицательные тесты часто включают проверку неправомерного ввода или взаимодействия с интерфейсом, которое должно приводить к сбоям или показу сообщений об ошибках.
  • Асимметричные ошибки: Важно тестировать и ситуации, когда компоненты могут ломаться при взаимодействии с внешними сервисами или при потере интернета. Для этого следует использовать мокирование запросов и сымитированные сбои.

Стратегия комбинирования положительных и отрицательных тестов

Для полного тестирования компонента, следует создавать тесты для обоих типов случаев. Однако важно, чтобы положительные и отрицательные тесты не перекрывались. Например, если компонент проверяет правильность email, то положительный тест должен проверить корректный формат, а отрицательный — некорректный.

При комбинировании этих тестов важно понимать, что тесты должны быть независимыми. То есть изменение состояния в одном тесте не должно влиять на результат другого. Это гарантирует стабильность тестового набора и позволяет быстро выявить источник ошибки.

Практическая рекомендация: Использование пользовательских событий

React Testing Library предлагает удобный API для взаимодействия с компонентами, который симулирует поведение реального пользователя. Для тестирования негативных сценариев часто используют события, такие как fireEvent.change, fireEvent.click, и методы из библиотеки user-event, которая позволяет симулировать более сложные действия пользователя (например, ввод с клавиатуры или взаимодействие с элементами).

import userEvent from '@testing-library/user-event';

test('показывает ошибку при пустом поле', async () => {
  render(<MyForm />);
  
  const inputName = screen.getByLabelText(/имя/i);
  const submitButton = screen.getByRole('button', { name: /отправить/i });
  
  await userEvent.type(inputName, '');
  userEvent.click(submitButton);
  
  const errorMessage = screen.getByText(/это поле обязательно/i);
  expect(errorMessage).toBeInTheDocument();
});

Использование user-event позволяет не только сымитировать действия, но и учитывать такие нюансы, как асинхронные события или сложные взаимодействия.

Ожидания и тайминги

Важно помнить, что многие компоненты работают асинхронно, особенно при взаимодействии с сервером или обработке сложных форм. В таких случаях необходимо использовать асинхронные ожидания, такие как findBy, waitFor, или waitForElementToBeRemoved, чтобы дождаться появления или исчезновения элементов на странице. Это помогает избежать ложных срабатываний тестов, когда элементы ещё не обновились.

test('должен показать сообщение о загрузке', async () => {
  render(<MyComponent />);
  
  fireEvent.click(screen.getByRole('button', { name: /загрузить/i }));
  
  // Ожидание появления индикатора загрузки
  const loadingMessage = await screen.findByText(/загрузка.../i);
  expect(loadingMessage).toBeInTheDocument();
});

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