Рендеринг списков с map

Одной из наиболее часто встречающихся задач при тестировании компонентов React является рендеринг списков. Когда список отображается на основе данных, часто используется метод map() для обхода массива и отображения каждого элемента. При тестировании таких компонентов важно правильно моделировать данные, проверять рендеринг каждого элемента списка и взаимодействовать с ними, чтобы убедиться, что приложение ведет себя как ожидается.

Основы рендеринга списков в React

Метод map() является стандартным способом отображения массивов в React. Например, если у нас есть список пользователей, мы можем отобразить его следующим образом:

function UserList({ users }) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Здесь users — это массив объектов, и мы отображаем каждый элемент с помощью map(). Каждый элемент списка требует уникального ключа (в данном случае user.id), чтобы React мог эффективно обновлять и повторно использовать компоненты при изменении состояния.

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

Для тестирования таких компонентов можно использовать React Testing Library, которая предоставляет простые и мощные инструменты для рендеринга компонентов и взаимодействия с ними. Рассмотрим, как протестировать компонент UserList.

import { render, screen } from '@testing-library/react';
import UserList from './UserList';

test('отрисовывает список пользователей', () => {
  const users = [
    { id: 1, name: 'Иван' },
    { id: 2, name: 'Мария' },
    { id: 3, name: 'Алексей' },
  ];

  render(<UserList users={users} />);

  // Проверяем, что все имена пользователей отрисованы
  users.forEach(user => {
    const userNameElement = screen.getByText(user.name);
    expect(userNameElement).toBeInTheDocument();
  });
});

Этот тест выполняет следующие шаги:

  1. Рендерит компонент UserList, передавая ему массив пользователей.
  2. Использует screen.getByText() для поиска каждого имени пользователя в документе.
  3. Проверяет, что каждый элемент списка присутствует на странице с помощью метода toBeInTheDocument().

Асинхронный рендеринг

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

Пример с асинхронной загрузкой данных:

import { useState, useEffect } from 'react';

function UserList() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetch('/api/users')
      .then(response => response.json())
      .then(data => setUsers(data));
  }, []);

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

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

import { render, screen, waitFor } from '@testing-library/react';
import UserList from './UserList';

test('загружает и отображает пользователей', async () => {
  global.fetch = jest.fn(() =>
    Promise.resolve({
      json: () => Promise.resolve([
        { id: 1, name: 'Иван' },
        { id: 2, name: 'Мария' },
      ]),
    })
  );

  render(<UserList />);

  await waitFor(() => screen.getByText('Иван'));

  expect(screen.getByText('Иван')).toBeInTheDocument();
  expect(screen.getByText('Мария')).toBeInTheDocument();
});

Здесь мы используем jest.fn() для создания заглушки для функции fetch, имитируя асинхронный ответ от API. Затем используем waitFor для ожидания, пока элементы не появятся в документе. Это важно для асинхронных операций, поскольку они могут занять некоторое время для выполнения.

Проверка корректности отображения элементов

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

Например, для проверки порядка рендеринга:

test('пользователи отображаются в правильном порядке', () => {
  const users = [
    { id: 1, name: 'Иван' },
    { id: 2, name: 'Мария' },
  ];

  render(<UserList users={users} />);

  const listItems = screen.getAllByRole('listitem');
  expect(listItems[0]).toHaveTextContent('Иван');
  expect(listItems[1]).toHaveTextContent('Мария');
});

В этом тесте мы получаем все элементы списка с помощью screen.getAllByRole('listitem'), а затем проверяем, что каждый элемент имеет ожидаемый текст. Также можно использовать toHaveTextContent, чтобы проверить содержимое элементов.

Проверка состояния пустого списка

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

Пример:

test('отображает сообщение о пустом списке, если нет пользователей', () => {
  const users = [];

  render(<UserList users={users} />);

  const emptyMessage = screen.getByText('Нет пользователей');
  expect(emptyMessage).toBeInTheDocument();
});

Здесь тестируется случай, когда список пуст, и нужно проверить, что компонент отображает сообщение “Нет пользователей”.

Имитация взаимодействия с элементами списка

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

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

test('при клике на пользователя вызывается обработчик', () => {
  const users = [{ id: 1, name: 'Иван' }];
  const onUserCl ick = jest.fn();

  render(<UserList users={users} onUserCl ick={onUserClick} />);

  const userElement = screen.getByText('Иван');
  userEvent.click(userElement);

  expect(onUserClick).toHaveBeenCalledWith(1);
});

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

Мокирование данных

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

Пример использования библиотеки msw для мокирования запросов:

import { render, screen, waitFor } from '@testing-library/react';
import { server, rest } from 'msw';
import UserList from './UserList';

beforeAll(() => {
  server.listen();
});

afterEach(() => {
  server.resetHandlers();
});

afterAll(() => {
  server.close();
});

test('загружает пользователей с мокированного API', async () => {
  server.use(
    rest.get('/api/users', (req, res, ctx) => {
      return res(
        ctx.json([
          { id: 1, name: 'Иван' },
          { id: 2, name: 'Мария' },
        ])
      );
    })
  );

  render(<UserList />);

  await waitFor(() => screen.getByText('Иван'));

  expect(screen.getByText('Иван')).toBeInTheDocument();
});

Здесь используется библиотека msw для мокирования HTTP-запросов, позволяя имитировать ответы от API без реальной работы с сервером.

Заключение

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