Обработка ошибок в асинхронном коде

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

Основы работы с асинхронными операциями

Асинхронный код в JavaScript может быть представлен через функции с промисами (Promise) или асинхронные функции (async/await). Для корректного тестирования таких операций необходимо правильно ждать завершения этих операций, чтобы убедиться, что компоненты корректно обрабатывают как успешные, так и ошибочные сценарии.

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

Стандартный подход к тестированию асинхронных компонентов

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

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

function MyComponent() {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchData() {
      try {
        const response = await fetch('https://api.example.com/data');
        if (!response.ok) throw new Error('Failed to fetch data');
        const result = await response.json();
        setData(result);
      } catch (error) {
        setError(error.message);
      }
    }
    
    fetchData();
  }, []);

  if (error) return <div>Error: {error}</div>;
  if (!data) return <div>Loading...</div>;

  return <div>{data}</div>;
}

export default MyComponent;

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

Тестирование с использованием Jest и Enzyme

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

Пример успешного теста
import React from 'react';
import { shallow } from 'enzyme';
import MyComponent from './MyComponent';

test('данные успешно загружаются', async () => {
  global.fetch = jest.fn().mockResolvedValue({
    ok: true,
    json: async () => ({ message: 'Success' }),
  });

  const wrapper = shallow(<MyComponent />);
  
  // Ожидаем завершения асинхронной операции
  await wrapper.update();

  expect(wrapper.text()).toContain('Success');
});

В данном примере используется мок-функция jest.fn().mockResolvedValue, чтобы подменить стандартный вызов fetch и вернуть успешный результат. Это позволяет проверить, что компонент правильно отрисовывает данные.

Пример теста с ошибкой
import React from 'react';
import { shallow } from 'enzyme';
import MyComponent from './MyComponent';

test('обрабатывает ошибку при загрузке данных', async () => {
  global.fetch = jest.fn().mockRejectedValue(new Error('Failed to fetch data'));

  const wrapper = shallow(<MyComponent />);
  
  // Ожидаем завершения асинхронной операции
  await wrapper.update();

  expect(wrapper.text()).toContain('Error: Failed to fetch data');
});

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

Использование waitFor для асинхронных ожиданий

Для тестов, в которых важно точно контролировать время ожидания асинхронных операций, можно использовать функцию waitFor из библиотеки @testing-library/react, которая будет ждать изменения состояния компонента после выполнения асинхронной операции:

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

test('отображает данные после загрузки', async () => {
  global.fetch = jest.fn().mockResolvedValue({
    ok: true,
    json: async () => ({ message: 'Success' }),
  });

  render(<MyComponent />);

  await waitFor(() => screen.getByText('Success'));

  expect(screen.getByText('Success')).toBeInTheDocument();
});

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

Мокирование ошибок с помощью Jest

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

Пример мокирования ошибки:

global.fetch = jest.fn().mockRejectedValueOnce(new Error('Network Error'));

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

Тестирование с таймерами и задержками

Когда компоненты используют таймеры или задержки, важно учитывать их влияние на асинхронные операции. Jest предоставляет встроенные функции для работы с таймерами, такие как jest.useFakeTimers() и jest.advanceTimersByTime(), которые позволяют контролировать поведение таймеров в тестах.

Пример с использованием фейковых таймеров:

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

function TimerComponent() {
  const [time, setTime] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setTime((prevTime) => prevTime + 1);
    }, 1000);
    return () => clearInterval(interval);
  }, []);

  return <div>{time}</div>;
}

export default TimerComponent;

Тестирование компонента с таймером:

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

test('таймер увеличивает значение каждую секунду', () => {
  jest.useFakeTimers();
  const wrapper = shallow(<TimerComponent />);

  // Прокачиваем время на 3 секунды
  jest.advanceTimersByTime(3000);

  expect(wrapper.text()).toBe('3');
});

Заключение

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