# TanStack Router Testing Examples
Complete examples for testing TanStack Router navigation, routes, and data loading.
## Test Setup
### Router Test Configuration
```typescript
// src/test/router-utils.tsx
import { ReactElement } from 'react';
import { render } from '@testing-library/react';
import { createMemoryHistory, RouterProvider, createRootRoute, createRoute, createRouter } from '@tanstack/react-router';
export function renderWithRouter(
ui: ReactElement,
{ initialEntries = ['/'] } = {}
) {
const rootRoute = createRootRoute({
component: () => ui,
});
const router = createRouter({
routeTree: rootRoute,
history: createMemoryHistory({ initialEntries }),
});
return render();
}
```
## Example 1: Testing Route Navigation
### Component with Navigation
```typescript
// src/components/Navigation.tsx
import { Link } from '@tanstack/react-router';
export function Navigation() {
return (
);
}
```
### Test Suite
```typescript
// src/components/Navigation.test.tsx
import { describe, it, expect } from 'vitest';
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { createRootRoute, createRoute, createRouter, RouterProvider } from '@tanstack/react-router';
import { render } from '@testing-library/react';
import { Navigation } from './Navigation';
function Home() {
return
Home Page
;
}
function About() {
return About Page
;
}
function Users() {
return Users Page
;
}
describe('Navigation', () => {
it('navigates between routes', async () => {
const user = userEvent.setup();
// Create routes
const rootRoute = createRootRoute({
component: () => (
<>
>
),
});
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: Home,
});
const aboutRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/about',
component: About,
});
const usersRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/users',
component: Users,
});
const routeTree = rootRoute.addChildren([indexRoute, aboutRoute, usersRoute]);
const router = createRouter({ routeTree });
render();
// Initially on home
expect(screen.getByText('Home Page')).toBeInTheDocument();
// Navigate to About
await user.click(screen.getByRole('link', { name: /about/i }));
expect(await screen.findByText('About Page')).toBeInTheDocument();
// Navigate to Users
await user.click(screen.getByRole('link', { name: /users/i }));
expect(await screen.findByText('Users Page')).toBeInTheDocument();
});
});
```
## Example 2: Testing Route Parameters
### Component Using Route Params
```typescript
// src/pages/UserProfile.tsx
import { useParams } from '@tanstack/react-router';
export function UserProfile() {
const { userId } = useParams({ from: '/users/$userId' });
return (
User Profile
User ID: {userId}
);
}
```
### Test Suite
```typescript
// src/pages/UserProfile.test.tsx
import { describe, it, expect } from 'vitest';
import { screen, render } from '@testing-library/react';
import { createRootRoute, createRoute, createRouter, RouterProvider } from '@tanstack/react-router';
import { createMemoryHistory } from '@tanstack/react-router';
import { UserProfile } from './UserProfile';
describe('UserProfile', () => {
it('displays correct user ID from route params', () => {
const rootRoute = createRootRoute();
const userRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/users/$userId',
component: UserProfile,
});
const routeTree = rootRoute.addChildren([userRoute]);
const history = createMemoryHistory({ initialEntries: ['/users/123'] });
const router = createRouter({ routeTree, history });
render();
expect(screen.getByTestId('user-id')).toHaveTextContent('User ID: 123');
});
it('updates when route params change', () => {
const rootRoute = createRootRoute();
const userRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/users/$userId',
component: UserProfile,
});
const routeTree = rootRoute.addChildren([userRoute]);
const history = createMemoryHistory({ initialEntries: ['/users/123'] });
const router = createRouter({ routeTree, history });
const { rerender } = render();
expect(screen.getByTestId('user-id')).toHaveTextContent('User ID: 123');
// Navigate to different user
history.push('/users/456');
rerender();
expect(screen.getByTestId('user-id')).toHaveTextContent('User ID: 456');
});
});
```
## Example 3: Testing Protected Routes
### Protected Route Component
```typescript
// src/components/ProtectedRoute.tsx
import { useAuth } from '../hooks/useAuth';
import { Navigate } from '@tanstack/react-router';
interface ProtectedRouteProps {
children: React.ReactNode;
}
export function ProtectedRoute({ children }: ProtectedRouteProps) {
const { isAuthenticated } = useAuth();
if (!isAuthenticated) {
return ;
}
return <>{children}>;
}
```
### Test Suite
```typescript
// src/components/ProtectedRoute.test.tsx
import { describe, it, expect, vi } from 'vitest';
import { screen, render } from '@testing-library/react';
import { createRootRoute, createRoute, createRouter, RouterProvider } from '@tanstack/react-router';
import { ProtectedRoute } from './ProtectedRoute';
import * as useAuthModule from '../hooks/useAuth';
vi.mock('../hooks/useAuth');
function Dashboard() {
return Dashboard
;
}
function Login() {
return Login Page
;
}
describe('ProtectedRoute', () => {
it('renders children when authenticated', () => {
vi.mocked(useAuthModule.useAuth).mockReturnValue({
isAuthenticated: true,
user: { id: '1', name: 'Alice' },
login: vi.fn(),
logout: vi.fn(),
});
const rootRoute = createRootRoute();
const dashboardRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/dashboard',
component: () => (
),
});
const loginRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/login',
component: Login,
});
const routeTree = rootRoute.addChildren([dashboardRoute, loginRoute]);
const router = createRouter({ routeTree });
render();
expect(screen.getByText('Dashboard')).toBeInTheDocument();
});
it('redirects to login when not authenticated', async () => {
vi.mocked(useAuthModule.useAuth).mockReturnValue({
isAuthenticated: false,
user: null,
login: vi.fn(),
logout: vi.fn(),
});
const rootRoute = createRootRoute();
const dashboardRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/dashboard',
component: () => (
),
});
const loginRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/login',
component: Login,
});
const routeTree = rootRoute.addChildren([dashboardRoute, loginRoute]);
const router = createRouter({ routeTree });
render();
// Should redirect and show login
expect(await screen.findByText('Login Page')).toBeInTheDocument();
expect(screen.queryByText('Dashboard')).not.toBeInTheDocument();
});
});
```
## Example 4: Testing Route Loaders
### Route with Loader
```typescript
// src/routes/user.tsx
import { createRoute } from '@tanstack/react-router';
interface User {
id: string;
name: string;
email: string;
}
export const userRoute = createRoute({
path: '/users/$userId',
loader: async ({ params }) => {
const response = await fetch(`/api/users/${params.userId}`);
if (!response.ok) throw new Error('User not found');
return response.json() as Promise;
},
component: function UserPage({ useLoaderData }) {
const user = useLoaderData();
return (
);
},
});
```
### Test Suite
```typescript
// src/routes/user.test.tsx
import { describe, it, expect, beforeAll, afterEach, afterAll } from 'vitest';
import { screen, render } from '@testing-library/react';
import { createRootRoute, createRouter, RouterProvider } from '@tanstack/react-router';
import { createMemoryHistory } from '@tanstack/react-router';
import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';
import { userRoute } from './user';
const server = setupServer(
http.get('/api/users/:userId', ({ params }) => {
return HttpResponse.json({
id: params.userId,
name: 'Alice Johnson',
email: 'alice@example.com',
});
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
describe('userRoute', () => {
it('loads and displays user data', async () => {
const rootRoute = createRootRoute();
const routeTree = rootRoute.addChildren([userRoute]);
const history = createMemoryHistory({ initialEntries: ['/users/123'] });
const router = createRouter({ routeTree, history });
render();
// Wait for loader to complete
expect(await screen.findByText('Alice Johnson')).toBeInTheDocument();
expect(screen.getByText('alice@example.com')).toBeInTheDocument();
});
it('handles loader error', async () => {
server.use(
http.get('/api/users/:userId', () => {
return new HttpResponse(null, { status: 404 });
})
);
const rootRoute = createRootRoute();
const routeTree = rootRoute.addChildren([userRoute]);
const history = createMemoryHistory({ initialEntries: ['/users/999'] });
const router = createRouter({ routeTree, history });
render();
// Should show error (TanStack Router handles this automatically)
expect(await screen.findByText(/error/i)).toBeInTheDocument();
});
});
```
## Key Takeaways
1. **Memory History**: Use `createMemoryHistory` for controlled navigation in tests
2. **Route Setup**: Build complete route trees with `createRouter` for realistic tests
3. **Params Testing**: Pass `initialEntries` to test routes with params
4. **Protected Routes**: Mock authentication context to test access control
5. **Loaders**: Use MSW to mock loader data fetching
---
**Next**: [TanStack Table Testing](tanstack-table-testing.md) | **Previous**: [Query Testing](tanstack-query-testing.md)