Утилиты для общего тестового кода

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

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

Вспомогательные функции для работы с компонентами

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

Пример утилиты для клика по кнопке

// utils/test-utils.js
export function clickButton(screen, buttonText) {
  const button = screen.getByText(buttonText);
  userEvent.click(button);
}

Эта утилита позволяет легко кликать по кнопке, передавая ей текст. Использование такой функции помогает избежать повторений в тестах.

import { render, screen } from '@testing-library/react';
import { clickButton } from './utils/test-utils';
import MyComponent from './MyComponent';

test('should perform an action when button is clicked', () => {
  render(<MyComponent />);
  clickButton(screen, 'Submit');
  expect(screen.getByText('Form submitted')).toBeInTheDocument();
});

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

Создание моков и заглушек для асинхронных вызовов

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

Утилита для мока данных с использованием msw

// utils/mock-api.js
import { rest } from 'msw';
import { setupServer } from 'msw/node';

const server = setupServer(
  rest.get('/api/user', (req, res, ctx) => {
    return res(ctx.json({ name: 'John Doe', age: 30 }));
  })
);

export function startServer() {
  server.listen();
}

export function stopServer() {
  server.close();
}

В этом примере используется библиотека MSW (Mock Service Worker) для мока API. Функции startServer и stopServer могут быть вызваны в тестах для имитации серверных запросов. Это позволяет тестировать поведение компонентов без зависимости от реальных серверных запросов.

import { render, screen, waitFor } from '@testing-library/react';
import { startServer, stopServer } from './utils/mock-api';
import UserProfile from './UserProfile';

beforeAll(() => startServer());
afterAll(() => stopServer());

test('renders user profile after API call', async () => {
  render(<UserProfile />);
  
  await waitFor(() => screen.getByText('John Doe'));
  
  expect(screen.getByText('John Doe')).toBeInTheDocument();
  expect(screen.getByText('30')).toBeInTheDocument();
});

Здесь UserProfile — это компонент, который запрашивает данные с API. Благодаря моку, тест не зависит от реального сервера, что делает его быстрым и независимым.

Работа с формами и их валидацией

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

Пример утилиты для заполнения формы

// utils/form-utils.js
import { userEvent } from '@testing-library/user-event';

export function fillForm(screen, data) {
  for (const [label, value] of Object.entries(data)) {
    const input = screen.getByLabelText(label);
    userEvent.type(input, value);
  }
}

Эта утилита позволяет быстро заполнять форму с данными, переданными в объекте. Например, для формы с полями «Имя» и «Email» можно передать объект с данными:

import { render, screen } from '@testing-library/react';
import { fillForm } from './utils/form-utils';
import MyForm from './MyForm';

test('submits form with correct data', async () => {
  render(<MyForm />);

  fillForm(screen, { 'Name': 'Alice', 'Email': 'alice@example.com' });

  const submitButton = screen.getByText('Submit');
  userEvent.click(submitButton);

  expect(screen.getByText('Form submitted')).toBeInTheDocument();
});

Утилита значительно сокращает код теста и улучшает его читаемость.

Управление состоянием компонентов

В некоторых случаях полезно создавать утилиты для работы с состоянием компонента, например, с состоянием модальных окон, сворачиваемых панелей и т. д. Утилита для управления состоянием может помочь при тестировании различных вариантов состояния компонента.

Утилита для открытия и закрытия модальных окон

// utils/modal-utils.js
import { userEvent } from '@testing-library/user-event';

export function openModal(screen) {
  const openButton = screen.getByText('Open Modal');
  userEvent.click(openButton);
}

export function closeModal(screen) {
  const closeButton = screen.getByText('Close Modal');
  userEvent.click(closeButton);
}

Эти утилиты позволяют эффективно тестировать открытие и закрытие модальных окон без дублирования кода.

import { render, screen } from '@testing-library/react';
import { openModal, closeModal } from './utils/modal-utils';
import ModalComponent from './ModalComponent';

test('should open and close modal', () => {
  render(<ModalComponent />);

  openModal(screen);
  expect(screen.getByText('Modal Content')).toBeInTheDocument();

  closeModal(screen);
  expect(screen.queryByText('Modal Content')).not.toBeInTheDocument();
});

Общие принципы при создании утилит

  1. Переиспользуемость: Утилиты должны быть независимыми и многократно используемыми в разных тестах. Они должны решать одну конкретную задачу, чтобы их можно было легко интегрировать в различные контексты.

  2. Читабельность: Важно, чтобы утилиты были простыми и понятными. Сложные, многозадачные утилиты только усложняют тесты. Каждый метод должен иметь очевидную задачу.

  3. Тестируемость: Утилиты должны быть хорошо тестируемыми. Для этого необходимо избегать логики, которая зависит от внешних условий (например, времени или состояния приложения).

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

Создание утилит для тестов — это один из способов повысить стабильность тестирования, улучшить читаемость и упростить поддержку тестов.