Тестирование защищенных маршрутов

Защищенные маршруты — это маршруты, доступ к которым должен предоставляться только определенным пользователям, например, аутентифицированным или обладающим конкретными ролями. В React Router реализовать это можно с помощью компонент-оберток и проверки состояния аутентификации.

Создание защищенного маршрута

Для начала создается компонент ProtectedRoute, который проверяет доступ пользователя:

import { Navigate } from 'react-router-dom';

function ProtectedRoute({ isAuthenticated, children }) {
  if (!isAuthenticated) {
    return <Navigate to="/login" replace />;
  }
  return children;
}
  • isAuthenticated — булевое значение, определяющее, авторизован ли пользователь.
  • Navigate — компонент React Router, выполняющий перенаправление при отсутствии доступа.
  • children — защищенный контент, который отображается только при успешной проверке.

Использование в маршрутах:

<Routes>
  <Route path="/dashboard" element={
    <ProtectedRoute isAuthenticated={user.isLoggedIn}>
      <Dashboard />
    </ProtectedRoute>
  } />
</Routes>

Основные сценарии тестирования

Тестирование защищенных маршрутов направлено на два ключевых аспекта:

  1. Доступ для авторизованных пользователей
  2. Блокировка для неавторизованных пользователей

Для тестирования можно использовать React Testing Library вместе с MemoryRouter из react-router-dom:

import { render, screen } from '@testing-library/react';
import { MemoryRouter, Routes, Route } from 'react-router-dom';
import ProtectedRoute from './ProtectedRoute';
import Dashboard from './Dashboard';
import Login from './Login';

test('показывает Dashboard для авторизованного пользователя', () => {
  render(
    <MemoryRouter initialEntries={['/dashboard']}>
      <Routes>
        <Route path="/login" element={<Login />} />
        <Route path="/dashboard" element={
          <ProtectedRoute isAuthenticated={true}>
            <Dashboard />
          </ProtectedRoute>
        } />
      </Routes>
    </MemoryRouter>
  );

  expect(screen.getByText(/dashboard/i)).toBeInTheDocument();
});

test('перенаправляет неавторизованного пользователя на Login', () => {
  render(
    <MemoryRouter initialEntries={['/dashboard']}>
      <Routes>
        <Route path="/login" element={<Login />} />
        <Route path="/dashboard" element={
          <ProtectedRoute isAuthenticated={false}>
            <Dashboard />
          </ProtectedRoute>
        } />
      </Routes>
    </MemoryRouter>
  );

  expect(screen.getByText(/login/i)).toBeInTheDocument();
});
  • MemoryRouter позволяет имитировать навигацию в тестах.
  • initialEntries задает начальный путь маршрутизатора.
  • Проверка осуществляется по видимому тексту компонента.

Тестирование ролей и сложных условий доступа

Если доступ зависит от ролей, ProtectedRoute расширяется:

function ProtectedRoute({ isAuthenticated, role, allowedRoles, children }) {
  if (!isAuthenticated) {
    return <Navigate to="/login" replace />;
  }
  if (!allowedRoles.includes(role)) {
    return <Navigate to="/unauthorized" replace />;
  }
  return children;
}

Тестирование маршрутов с ролями:

test('пользователь с ролью "admin" получает доступ', () => {
  render(
    <MemoryRouter initialEntries={['/admin']}>
      <Routes>
        <Route path="/unauthorized" element={<Unauthorized />} />
        <Route path="/admin" element={
          <ProtectedRoute isAuthenticated={true} role="admin" allowedRoles={['admin']}>
            <AdminPanel />
          </ProtectedRoute>
        } />
      </Routes>
    </MemoryRouter>
  );

  expect(screen.getByText(/admin panel/i)).toBeInTheDocument();
});

test('пользователь с ролью "user" перенаправляется на Unauthorized', () => {
  render(
    <MemoryRouter initialEntries={['/admin']}>
      <Routes>
        <Route path="/unauthorized" element={<Unauthorized />} />
        <Route path="/admin" element={
          <ProtectedRoute isAuthenticated={true} role="user" allowedRoles={['admin']}>
            <AdminPanel />
          </ProtectedRoute>
        } />
      </Routes>
    </MemoryRouter>
  );

  expect(screen.getByText(/unauthorized/i)).toBeInTheDocument();
});

Важные моменты при тестировании

  • Использование MemoryRouter обязательно для тестов, чтобы не требовался реальный браузер.
  • Всегда проверяется как положительное, так и отрицательное поведение.
  • Для сложных условий можно создавать фабрики пользователей с разными уровнями доступа.
  • Проверка должна быть динамической, особенно если маршруты и роли часто изменяются.
  • Желательно имитировать реальные состояния аутентификации с помощью моков контекста или Redux-стора.

Отладка и рекомендации

  • Логирование role и isAuthenticated помогает выявить ошибки в логике маршрутов.
  • В случае большого количества маршрутов лучше писать отдельные тестовые утилиты для генерации тестовых конфигураций.
  • При изменении React Router версии нужно проверять корректность работы Navigate и MemoryRouter, так как API может изменяться.

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