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! 🚀