For my new project I decided to jump from React Router 7 to TanStack Router 🔥, especially since I’d been loving TanStack Query and figured the synergy between the two would be really powerful. But when it came time to write tests, I couldn’t find much documentation on wiring up TanStack Router in React Testing Library. That’s why I built this tiny helper 🛠️

Its a file-based implementation

Full Code

test-utils.ts

import React from 'react'
import {
  Outlet,
  RouterProvider,
  createMemoryHistory,
  createRootRoute,
  createRoute,
  createRouter,
} from '@tanstack/react-router'
import { render, screen } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

/**
 * Renders a component under:
 *  - a minimal TanStack Router instance (memory history),
 *  - optionally wrapped in a given QueryClientProvider.
 *
 * @param Component        The React component to mount.
 * @param path             The initial URL path.
 * @param queryClient?     If provided, wraps the render in .
 *
 * @returns { router, renderResult, queryClient? }
 */

export async function renderWithProviders(
  Component: React.ComponentType,
  path: string,
  queryClient?: QueryClient,
) {
  // Root route with minimal Outlet for rendering child routes
  const rootRoute = createRootRoute({
    component: () => (
      <>
        <div data-testid="root-layout"></div>
        <Outlet />
      </>
    ),
  })

  // Index route, so “/” always matches
  const indexRoute = createRoute({
    getParentRoute: () => rootRoute,
    path: '/',
    component: () => <div>Index</div>,
  })

  // Test route mounting your Component at `path`
  const testRoute = createRoute({
    getParentRoute: () => rootRoute,
    path,
    component: () => <Component />,
  })

  // Create the router instance with memory history
  const router = createRouter({
    routeTree: rootRoute.addChildren([indexRoute, testRoute]),
    history: createMemoryHistory({ initialEntries: [path] }),
    defaultPendingMinMs: 0,
  })

  // Build the render tree and add QueryClientProvider if provided
  let tree = <RouterProvider router={router} />
  if (queryClient) {
    tree = (
      <QueryClientProvider client={queryClient}>{tree}</QueryClientProvider>
    )
  }

  // Render and wait for the route to resolve and the component to mount
  const renderResult = render(tree)
  await screen.findByTestId('root-layout')

  return { router, renderResult }
}

Create a Minimal Root Route

const rootRoute = createRootRoute({
  component: () => (
    <>
      <div data-testid="root-layout" />
      <Outlet />
    </>
  ),
})
  • Renders a
    plus an so we can wait for router hydration.

Add an Index Route (/)

const indexRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/',
  component: () => <div>Index</div>,
})
  • Simple placeholder for index.
  • Ensures that navigating to "/" always resolves without errors.

Add Your Test Route (path)

const testRoute = createRoute({
  getParentRoute: () => rootRoute,
  path,
  component: () => <Component />,
})
  • Mounts the component under test at whatever URL you pass in.

Instantiate the Router

const router = createRouter({
  routeTree: rootRoute.addChildren([indexRoute, testRoute]),
  history: createMemoryHistory({ initialEntries: [path] }),
  defaultPendingMinMs: 0,
})
  • routeTree: Combines rootRoute with its children (indexRoute, testRoute).
  • createMemoryHistory: Starts the router at the desired path.
  • defaultPendingMinMs: 0: Speeds up transition resolution during testing by removing any artificial delay.

Build the Render Tree

let tree = <RouterProvider router={router}/>
if (queryClient)
  tree = <QueryClientProvider client={queryClient}>{tree}</QueryClientProvider>
  • : Supplies the router context to your components.
  • Optional Query Client: If you passed queryClient, wrap the router in so your components can use React Query hooks.

Render and Await Hydration

const renderResult = render(tree)
await screen.findByTestId('root-layout')
  • render(tree): Uses React Testing Library to render the component tree.
  • findByTestId('root-layout'): Waits until the router’s root layout is mounted, guaranteeing that navigation and route resolution are complete before assertions.

Return Utilities for Tests

return { router, renderResult }
  • router: Access the router instance in your test for navigation, state inspection, etc.
  • renderResult: Provides all the usual Testing Library query utilities and can be usefull for testing accesibility

Example Usage

import { renderWithProviders } from './test-utils'
import { QueryClient } from '@tanstack/react-query'
import MyComponent from './MyComponent'
import { screen } from '@testing-library/react'

// Optional: for accessibility testing
import { axe, toHaveNoViolations } from 'jest-axe'


test('it shows header on /dashboard', async () => {
  const queryClient = new QueryClient()
  await renderWithProviders(MyComponent, '/dashboard', queryClient)

  // use screen + specify heading level
  expect(
    screen.getByRole('heading', { level: 1, name: /dashboard/i })
  ).toBeInTheDocument()
})

// Optional accessibility check with axe:
test('should have no accessibility violations', async () => {
  const queryClient = new QueryClient()
  const { container } = await renderWithProviders(MyComponent, '/dashboard', queryClient)

  const results = await axe(container)
  expect(results).toHaveNoViolations()
})

In Summary

Thanks for reading! 🎉 I hope this helper makes your TanStack Router tests a breeze. If you have ideas, questions, or improvements, drop a comment below—let’s keep learning together. Happy testing! 🚀