Симуляция ошибок и network failures

Тестирование компонентов React часто требует имитации различных сценариев работы с сетью, включая ошибки и сбои соединения. Это позволяет проверить устойчивость приложения, корректность обработки исключений и поведение UI при нештатных ситуациях. React Testing Library (RTL) сама по себе фокусируется на взаимодействии с DOM, поэтому для симуляции сетевых запросов используется комбинация RTL и инструментов для мокирования API, таких как MSW (Mock Service Worker) или jest-fetch-mock.


Мокирование fetch и axios

Для большинства приложений сетевые запросы выполняются через fetch или axios. Чтобы имитировать ошибки:

1. Использование jest-fetch-mock

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

beforeEach(() => {
  fetch.resetMocks();
});

test('показывает сообщение об ошибке при сбое fetch', async () => {
  fetch.mockReject(() => Promise.reject('Network error'));

  render(<MyComponent />);

  await waitFor(() => {
    expect(screen.getByText(/ошибка загрузки/i)).toBeInTheDocument();
  });
});

Здесь fetch.mockReject позволяет имитировать сетевой сбой, а waitFor гарантирует асинхронное ожидание обновления DOM после обработки ошибки компонентом.

2. Использование axios с jest.mock

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

jest.mock('axios');

test('отображает ошибку при отказе axios', async () => {
  axios.get.mockRejectedValue(new Error('Request failed'));

  render(<MyComponent />);

  await waitFor(() => {
    expect(screen.getByText(/не удалось загрузить данные/i)).toBeInTheDocument();
  });
});

Мокирование axios через jest.mock позволяет контролировать ответы и эмулировать сетевые ошибки без изменения оригинального кода компонента.


Использование Mock Service Worker (MSW)

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

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

const server = setupServer(
  rest.get('/api/data', (req, res, ctx) => {
    return res(ctx.status(500), ctx.json({ message: 'Internal Server Error' }));
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

test('обрабатывает 500 ошибку сервера', async () => {
  render(<MyComponent />);

  await waitFor(() => {
    expect(screen.getByText(/внутренняя ошибка сервера/i)).toBeInTheDocument();
  });
});

Преимущества MSW:

  • Позволяет имитировать сетевые задержки через ctx.delay().
  • Можно динамически менять ответы сервера в каждом тесте.
  • Не требует изменения исходного кода компонента для тестирования сетевых ошибок.

Симуляция таймаутов и медленной сети

Таймауты часто приводят к состоянию ожидания (loading) и требуют проверки корректного отображения индикаторов загрузки.

server.use(
  rest.get('/api/data', (req, res, ctx) => {
    return res(ctx.delay(3000), ctx.json({ data: [] }));
  })
);

test('отображает индикатор загрузки при медленном ответе', async () => {
  render(<MyComponent />);

  expect(screen.getByText(/загрузка/i)).toBeInTheDocument();
});

Здесь ctx.delay(3000) имитирует медленное соединение, проверяя реакцию UI на длительные сетевые операции.


Обработка ошибок в UI

Правильная обработка ошибок предполагает:

  • Показ пользовательского уведомления (toast, alert, div с сообщением).
  • Возможность повторной попытки запроса.
  • Сброс состояния ошибки после повторного успешного запроса.

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

server.use(
  rest.get('/api/data', (req, res, ctx) => res(ctx.status(500))),
);

test('позволяет повторить запрос после ошибки', async () => {
  render(<MyComponent />);

  await waitFor(() => {
    expect(screen.getByText(/ошибка загрузки/i)).toBeInTheDocument();
  });

  server.use(
    rest.get('/api/data', (req, res, ctx) => res(ctx.json({ items: [1,2,3] }))),
  );

  screen.getByText(/повторить/i).click();

  await waitFor(() => {
    expect(screen.getByText(/элемент 1/i)).toBeInTheDocument();
  });
});

Лучшие практики

  • Изолированное мокирование: каждый тест должен определять собственные сценарии сетевых ошибок.
  • Использовать waitFor для асинхронных обновлений DOM, а не прямые expect после render.
  • Проверять пользовательский опыт, а не внутреннюю реализацию: тесты должны подтверждать корректное отображение сообщений об ошибках и состояния загрузки.
  • Разделение тестов по типам ошибок: сетевой сбой, ошибка сервера, таймаут, некорректные данные.

Эти методы обеспечивают комплексное покрытие сценариев network failures, повышают надежность компонентов и делают тесты более реалистичными и устойчивыми к изменению API.