Экспорт и переиспользование test utils

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

Основные принципы переиспользуемых утилит

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

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

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

Пример:

// utils/renderWithProviders.js
import { render } from '@testing-library/react';
import { MyContextProvider } from '../context';

export function renderWithProviders(ui, { providerProps = {} } = {}) {
  return render(
    <MyContextProvider {...providerProps}>
      {ui}
    </MyContextProvider>
  );
}

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

Теперь, чтобы использовать эту утилиту в тестах, достаточно импортировать её и передать нужный компонент:

import { renderWithProviders } from './utils/renderWithProviders';
import MyComponent from './MyComponent';

test('MyComponent renders correctly', () => {
  const { getByText } = renderWithProviders(<MyComponent />);
  expect(getByText(/Hello/)).toBeInTheDocument();
});

Взаимодействие с элементами

Ещё одна важная часть тестирования — это взаимодействие с элементами в компоненте. Часто используются операции типа клика по кнопке или ввода текста в поле. Эти действия можно абстрагировать в отдельные утилиты, чтобы упростить тесты.

Пример утилиты для клика:

// utils/clickButton.js
import { fireEvent } from '@testing-library/react';

export function clickButton(button) {
  fireEvent.click(button);
}

Теперь, в тестах можно использовать эту утилиту для кликов по кнопкам:

import { clickButton } from './utils/clickButton';
import { renderWithProviders } from './utils/renderWithProviders';
import MyComponent from './MyComponent';

test('MyComponent triggers the event on button click', () => {
  const { getByText } = renderWithProviders(<MyComponent />);
  const button = getByText(/Click me/);
  clickButton(button);
  expect(getByText(/Clicked/)).toBeInTheDocument();
});

Ожидания

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

Пример:

// utils/waitForElement.js
import { waitFor } from '@testing-library/react';

export async function waitForElement(element, options = {}) {
  return await waitFor(() => {
    expect(element).toBeInTheDocument();
  }, options);
}

Использование этой утилиты:

import { renderWithProviders } from './utils/renderWithProviders';
import { waitForElement } from './utils/waitForElement';
import MyComponent from './MyComponent';

test('MyComponent waits for async content', async () => {
  const { getByText } = renderWithProviders(<MyComponent />);
  const asyncElement = await getByText(/Loading.../);
  await waitForElement(asyncElement);
  expect(getByText(/Content loaded/)).toBeInTheDocument();
});

Создание mock-данных и фейковых API

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

Пример утилиты для мокирования данных:

// utils/mockApi.js
export function mockApi(response = { data: [] }) {
  jest.spyOn(global, 'fetch').mockResolvedValueOnce({
    json: jest.fn().mockResolvedValue(response),
  });
}

Использование утилиты в тестах:

import { mockApi } from './utils/mockApi';
import { renderWithProviders } from './utils/renderWithProviders';
import MyComponent from './MyComponent';

test('MyComponent fetches data correctly', async () => {
  mockApi({ data: [{ id: 1, name: 'Test' }] });
  const { getByText } = renderWithProviders(<MyComponent />);
  expect(getByText(/Loading.../)).toBeInTheDocument();
  await waitForElement(getByText(/Test/));
});

Структура утилит

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

/utils
  /renderWithProviders.js
  /clickButton.js
  /waitForElement.js
  /mockApi.js

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

Заключение

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