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

Universal Router предоставляет возможность создавать маршруты, которые возвращают результаты асинхронно. Это особенно важно для современных приложений, где загрузка данных с сервера или динамическая генерация компонентов требует использования промисов или async/await. Асинхронный маршрут определяется через функцию action, которая может быть async или возвращать промис.

const router = new UniversalRouter([
  {
    path: '/user/:id',
    async action(context) {
      const response = await fetch(`/api/users/${context.params.id}`);
      const user = await response.json();
      return user;
    }
  }
]);

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


Контекст маршрута

Объект context в Universal Router включает ключевые свойства, которые необходимы для работы с асинхронными маршрутами:

  • pathname – текущий путь URL.
  • params – объект с параметрами маршрута, например :id.
  • query – объект с параметрами запроса из URL.
  • baseUrl – базовый путь, если маршруты вложенные.
  • route – текущий маршрут, что полезно для рекурсивного вызова маршрутов.

Асинхронная функция action может использовать все эти свойства для получения данных, логирования или перенаправлений.


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

Тестирование асинхронных маршрутов требует учета нескольких особенностей:

  1. Промисы и async/await Каждый маршрут, возвращающий промис, должен тестироваться с использованием асинхронного кода. В Jest или Mocha тест может выглядеть так:
test('маршрут /user/:id возвращает пользователя', async () => {
  const router = new UniversalRouter([
    {
      path: '/user/:id',
      async action({ params }) {
        return { id: params.id, name: 'Alice' };
      }
    }
  ]);

  const result = await router.resolve({ pathname: '/user/42' });
  expect(result).toEqual({ id: '42', name: 'Alice' });
});
  1. Обработка ошибок Асинхронные маршруты могут выбрасывать исключения при ошибках запроса или неверных данных. Тест должен проверять корректную работу в таких случаях:
test('маршрут выбрасывает ошибку при недоступном пользователе', async () => {
  const router = new UniversalRouter([
    {
      path: '/user/:id',
      async action({ params }) {
        if (params.id !== '1') throw new Error('User not found');
        return { id: '1', name: 'Alice' };
      }
    }
  ]);

  await expect(router.resolve({ pathname: '/user/42' }))
    .rejects
    .toThrow('User not found');
});
  1. Мока функции fetch или API-запросов Для реальных асинхронных маршрутов часто требуется мокирование сетевых запросов:
global.fetch = jest.fn(() =>
  Promise.resolve({
    json: () => Promise.resolve({ id: '42', name: 'Alice' })
  })
);

test('маршрут /user/:id возвращает данные из fetch', async () => {
  const router = new UniversalRouter([
    {
      path: '/user/:id',
      async action({ params }) {
        const response = await fetch(`/api/users/${params.id}`);
        return await response.json();
      }
    }
  ]);

  const result = await router.resolve({ pathname: '/user/42' });
  expect(result).toEqual({ id: '42', name: 'Alice' });
  expect(fetch).toHaveBeenCalledWith('/api/users/42');
});

Вложенные асинхронные маршруты

Universal Router поддерживает вложенные маршруты, где каждый уровень может быть асинхронным:

const router = new UniversalRouter([
  {
    path: '/user/:id',
    async action({ params, next }) {
      const user = await fetchUser(params.id);
      const details = await next();
      return { user, details };
    },
    children: [
      {
        path: '/posts',
        async action({ params }) {
          return fetchPosts(params.id);
        }
      }
    ]
  }
]);

Тестирование таких маршрутов требует вызова resolve с полным путем и проверки объединенного результата:

const result = await router.resolve({ pathname: '/user/42/posts' });
expect(result).toEqual({
  user: { id: '42', name: 'Alice' },
  details: [{ id: 1, title: 'Post 1' }]
});

Асинхронные редиректы

Асинхронные маршруты могут возвращать объект с ключом redirect, чтобы инициировать перенаправление:

async action({ params }) {
  if (!params.id) return { redirect: '/login' };
  return { id: params.id, name: 'Alice' };
}

Тестирование редиректов включает проверку возвращаемого объекта:

const result = await router.resolve({ pathname: '/user/' });
expect(result).toEqual({ redirect: '/login' });

Советы по тестированию

  • Всегда использовать await при вызове router.resolve, иначе тесты не дождутся промисов.
  • Для мокирования сетевых запросов применять Jest или sinon, чтобы исключить внешние зависимости.
  • Проверять как успешные сценарии, так и обработку ошибок.
  • Для сложных вложенных маршрутов тестировать отдельные уровни и полный путь одновременно.

Асинхронные маршруты делают Universal Router гибким инструментом, но требуют внимательного подхода при тестировании, особенно при взаимодействии с внешними API или при динамическом формировании данных.