Тестирование useEffect через компонент

useEffect — это хук React, который позволяет выполнять побочные эффекты в функциональных компонентах. Побочные эффекты могут включать в себя такие операции, как получение данных с сервера, подписка на события или изменение DOM. Одной из сложных задач при тестировании компонентов с использованием useEffect является правильная имитация побочных эффектов и проверка их поведения в различных сценариях.

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

Структура компонента с useEffect

Пример компонента, который делает запрос к серверу при монтировании и обновляет состояние при изменении пропса:

import React, { useState, useEffect } from 'react';

const DataFetcher = ({ url }) => {
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch(url);
      const result = await response.json();
      setData(result);
    };

    fetchData();
  }, [url]);

  return (
    <div>
      {data ? (
        <pre>{JSON.stringify(data, null, 2)}</pre>
      ) : (
        <p>Loading...</p>
      )}
    </div>
  );
};

Этот компонент делает запрос к серверу с помощью useEffect и отображает загруженные данные или сообщение “Loading…”. При изменении пропса url, хук useEffect повторно выполняет запрос.

Стратегии тестирования компонентов с useEffect

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

1. Мокирование асинхронных операций

Если компонент зависит от асинхронной операции, например, запроса на сервер, тестирование можно проводить с помощью моков (mock). Для этого можно использовать такие библиотеки, как jest.mock() для имитации асинхронных вызовов.

Пример мока запроса:

import { shallow } from 'enzyme';
import DataFetcher from './DataFetcher';

jest.mock('fetch', () => jest.fn());

describe('DataFetcher', () => {
  it('отображает данные после загрузки', async () => {
    const mockData = { key: 'value' };
    fetch.mockResolvedValueOnce({
      json: () => Promise.resolve(mockData),
    });

    const wrapper = shallow(<DataFetcher url="https://api.example.com/data" />);
    
    // Ждем, пока компонент загрузит данные
    await new Promise(setImmediate);  // Имитация асинхронности

    wrapper.update();

    expect(wrapper.text()).toContain(JSON.stringify(mockData, null, 2));
  });
});

В этом примере fetch мокируется, и вместо настоящего запроса возвращается заранее подготовленный ответ. Мы проверяем, что компонент корректно отобразил данные после их загрузки.

2. Тестирование изменения пропсов

При изменении пропсов компонент, использующий useEffect, должен правильно обновить свое состояние. Чтобы проверить это, можно использовать метод setProps() в Enzyme, который позволяет изменять пропсы компонента после его монтирования.

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

it('перезапускает запрос при изменении URL', async () => {
  const firstData = { key: 'first' };
  const secondData = { key: 'second' };

  fetch.mockResolvedValueOnce({
    json: () => Promise.resolve(firstData),
  });

  const wrapper = shallow(<DataFetcher url="https://api.example.com/first" />);
  await new Promise(setImmediate);  // Ждем асинхронной загрузки

  wrapper.update();
  expect(wrapper.text()).toContain(JSON.stringify(firstData, null, 2));

  // Меняем URL
  fetch.mockResolvedValueOnce({
    json: () => Promise.resolve(secondData),
  });
  
  wrapper.setProps({ url: "https://api.example.com/second" });
  await new Promise(setImmediate);

  wrapper.update();
  expect(wrapper.text()).toContain(JSON.stringify(secondData, null, 2));
});

Здесь компонент сначала делает запрос по первому URL, а затем, при изменении пропса url, перезапускает запрос с новым значением. Проверяется, что данные обновляются в UI.

3. Тестирование поведения при ошибке

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

Пример теста на ошибку:

it('обрабатывает ошибку при неудачном запросе', async () => {
  fetch.mockRejectedValueOnce(new Error('Network error'));

  const wrapper = shallow(<DataFetcher url="https://api.example.com/data" />);
  await new Promise(setImmediate);

  wrapper.update();

  expect(wrapper.text()).toContain('Ошибка загрузки данных');
});

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

4. Проверка выполнения useEffect только один раз

Чтобы убедиться, что побочный эффект выполняется только один раз при монтировании компонента, можно использовать jest.spyOn() для отслеживания вызова функции внутри useEffect.

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

it('выполняет useEffect только один раз', () => {
  const mockEffect = jest.fn();
  const wrapper = shallow(<DataFetcher url="https://api.example.com/data" />);
  
  // Заглушка для useEffect
  wrapper.instance().componentDidMount = mockEffect;
  
  expect(mockEffect).toHaveBeenCalledTimes(1);
});

Это полезно для компонентов, где не нужно повторное выполнение эффекта, например, при изменении состояния или пропсов.

Заключение

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