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

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


Мокирование маршрутов

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

import { createRouter, RouterProvider } from '@tanstack/router';
import { render } from '@testing-library/react';
import App from './App';

const testRouter = createRouter({
  routeTree: [
    {
      path: '/',
      component: App,
    },
    {
      path: '/profile/:id',
      component: Profile,
    },
  ],
});

render(
  <RouterProvider router={testRouter}>
    <App />
  </RouterProvider>
);

Ключевой момент: оборачивать тестируемый компонент в RouterProvider с тестовым экземпляром маршрутизатора, чтобы маршруты работали так же, как в реальном приложении.


Использование navigate и проверка переходов

TanStack Router использует функцию navigate для изменения маршрутов. В тестах можно эмулировать навигацию и проверять, как компонент реагирует на изменение пути:

import { act } from '@testing-library/react';

act(() => {
  testRouter.navigate('/profile/42');
});

После вызова navigate состояние маршрутизатора обновляется, и тестируемый компонент получает новые параметры маршрута. Это позволяет проверять:

  • Рендер правильного компонента на новом маршруте.
  • Отображение корректных данных на основе параметров маршрута (params).
  • Реакцию на динамические сегменты маршрута.

Тестирование динамических маршрутов и параметров

Компоненты часто зависят от динамических сегментов маршрута, например /profile/:id. В TanStack Router эти значения доступны через объект useParams:

import { useParams } from '@tanstack/router';

function Profile() {
  const { id } = useParams();
  return <div>Profile ID: {id}</div>;
}

В тестах важно эмулировать параметры маршрута, чтобы убедиться, что компонент корректно их использует:

testRouter.navigate('/profile/123');

const { getByText } = render(
  <RouterProvider router={testRouter}>
    <Profile />
  </RouterProvider>
);

expect(getByText('Profile ID: 123')).toBeInTheDocument();

Проверка состояния маршрутизатора

TanStack Router хранит состояние, которое можно использовать для проверки в тестах:

  • router.state.currentLocation — текущий путь.
  • router.state.matches — список всех активных маршрутов.

Пример проверки перехода:

act(() => {
  testRouter.navigate('/profile/7');
});

expect(testRouter.state.currentLocation.pathname).toBe('/profile/7');
expect(testRouter.state.matches.some(match => match.route.path === '/profile/:id')).toBe(true);

Это позволяет удостовериться, что переход произошёл правильно, а маршрутизатор корректно сопоставил путь с маршрутом.


Работа с асинхронными данными в маршрутах

TanStack Router поддерживает асинхронные функции loader для предзагрузки данных перед рендером компонента. В тестах необходимо использовать async/await и проверять загрузку данных:

const profileRoute = {
  path: '/profile/:id',
  component: Profile,
  loader: async ({ params }) => {
    return fetch(`/api/profile/${params.id}`).then(res => res.json());
  },
};

await act(async () => {
  await testRouter.navigate('/profile/5');
});

expect(fetch).toHaveBeenCalledWith('/api/profile/5');

Важно эмулировать ответы API через моки, чтобы тесты были детерминированными.


Интеграционное тестирование навигации

Для комплексного тестирования часто создают интеграционные тесты, где проверяются:

  • Рендер главного компонента.
  • Навигация по ссылкам (Link или navigate).
  • Корректное обновление URL и параметров.
  • Асинхронная загрузка данных через loader.

Пример:

import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

render(
  <RouterProvider router={testRouter}>
    <App />
  </RouterProvider>
);

userEvent.click(screen.getByText('Go to Profile'));
expect(testRouter.state.currentLocation.pathname).toBe('/profile/1');
expect(screen.getByText('Profile ID: 1')).toBeInTheDocument();

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


Использование вспомогательных функций

Для упрощения тестов создают вспомогательные функции:

  • renderWithRouter — обёртка для тестирования компонентов с маршрутизатором.
  • navigateTo — функция для быстрого перехода на нужный маршрут.
  • mockLoader — эмуляция асинхронных загрузок данных.

Пример обёртки:

function renderWithRouter(ui, { route = '/' } = {}) {
  testRouter.navigate(route);
  return render(<RouterProvider router={testRouter}>{ui}</RouterProvider>);
}

Это делает тесты более читаемыми и уменьшает повторение кода.


Особенности тестирования защищённых маршрутов

Если приложение использует авторизацию или защиту маршрутов, необходимо:

  • Эмулировать состояние пользователя в тестах.
  • Проверять редиректы на страницы входа или ошибки доступа.
  • Удостоверяться, что компоненты не рендерятся без нужных прав.
testRouter.navigate('/admin');
expect(testRouter.state.currentLocation.pathname).toBe('/login');

TanStack Router позволяет легко интегрировать такие проверки через loader или beforeEnter хуки.


Практические рекомендации

  • Использовать тестовый экземпляр маршрутизатора, чтобы тесты не зависели от состояния основного приложения.
  • Проверять параметры маршрута и активные маршруты после навигации.
  • Эмулировать асинхронные загрузки через моки.
  • Создавать интеграционные тесты для проверки навигации и переходов между страницами.
  • Использовать вспомогательные функции для уменьшения повторения кода и повышения читаемости тестов.

Эти методы обеспечивают надежное и детерминированное тестирование компонентов, зависящих от TanStack Router, включая динамические маршруты, асинхронные данные и защиту маршрутов.