Тестирование loader-функций

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

Структура loader-функции

Loader-функция — это асинхронная функция, принимающая объект с параметрами маршрута, и возвращающая данные, которые будут переданы компоненту маршрута:

async function loader({ params, request }) {
  const response = await fetch(`/api/items/${params.id}`);
  if (!response.ok) throw new Error('Failed to fetch item');
  return response.json();
}

Ключевые моменты для тестирования:

  • Правильная обработка параметров маршрута (params и request).
  • Корректная обработка ошибок.
  • Возврат ожидаемого формата данных.

Мокирование зависимостей

Для тестирования loader-функций важно изолировать их от реальных сетевых запросов или других побочных эффектов. Наиболее распространённые подходы:

  1. Jest и глобальный fetch:
global.fetch = jest.fn();

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

test('loader возвращает данные', async () => {
  fetch.mockResolvedValueOnce({
    ok: true,
    json: async () => ({ id: 1, name: 'Item 1' })
  });

  const data = await loader({ params: { id: '1' } });
  expect(data).toEqual({ id: 1, name: 'Item 1' });
});
  1. Мокирование модулей с помощью jest.mock:

Если данные загружаются через отдельный модуль API:

import * as api from './api';
jest.mock('./api');

api.getItem.mockResolvedValue({ id: 1, name: 'Item 1' });

test('loader вызывает API и возвращает данные', async () => {
  const data = await loader({ params: { id: '1' } });
  expect(api.getItem).toHaveBeenCalledWith('1');
  expect(data).toEqual({ id: 1, name: 'Item 1' });
});

Проверка обработки ошибок

Loader должен корректно обрабатывать ошибки, например при сетевых сбоях или некорректных данных. Тесты для таких случаев обеспечивают стабильность маршрутов:

test('loader выбрасывает ошибку при неуспешном fetch', async () => {
  fetch.mockResolvedValueOnce({ ok: false });

  await expect(loader({ params: { id: '1' } })).rejects.toThrow('Failed to fetch item');
});

Также важно тестировать сценарии с отсутствующими параметрами:

test('loader выбрасывает ошибку при отсутствии параметра id', async () => {
  await expect(loader({ params: {} })).rejects.toThrow();
});

Интеграционное тестирование с маршрутизатором

Loader-функции тесно связаны с маршрутизатором. Для комплексной проверки можно использовать render маршрута в тестовой среде:

import { createMemoryRouter, RouterProvider } from '@tanstack/router';
import { render, screen, waitFor } from '@testing-library/react';
import routes from './routes';

test('маршрут корректно загружает данные через loader', async () => {
  const router = createMemoryRouter(routes, { initialEntries: ['/items/1'] });
  render(<RouterProvider router={router} />);

  await waitFor(() => screen.getByText('Item 1'));
  expect(screen.getByText('Item 1')).toBeInTheDocument();
});

Такой подход проверяет не только loader, но и интеграцию с компонентами маршрута, рендеринг данных и обработку асинхронной логики.

Советы по организации тестов

  • Разделять unit-тесты loader и интеграционные тесты маршрутов.
  • Использовать мокированные API для unit-тестов и реальную структуру маршрутов для интеграционных.
  • Покрывать сценарии успешной загрузки, ошибки, отсутствия параметров и некорректных данных.
  • Использовать async/await и waitFor для тестирования асинхронного поведения.

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