Skip to content

Instantly share code, notes, and snippets.

@truongluu
Created November 2, 2024 06:27
Show Gist options
  • Save truongluu/c12852dc6fdc357b7b70f34af2ee78e5 to your computer and use it in GitHub Desktop.
Save truongluu/c12852dc6fdc357b7b70f34af2ee78e5 to your computer and use it in GitHub Desktop.
Memory Router Testing
// src/test/test-utils.tsx
import { render } from '@testing-library/react'
import { MemoryRouter, Routes, Route } from 'react-router-dom'
// Enhanced render utility that accepts route configuration
interface RenderWithRouterOptions {
route?: string
initialEntries?: string[]
initialIndex?: number
}
export function renderWithRouter(
ui: React.ReactElement,
{
route = '/',
initialEntries = [route],
initialIndex,
...renderOptions
}: RenderWithRouterOptions = {}
) {
return {
...render(
<MemoryRouter initialEntries={initialEntries} initialIndex={initialIndex}>
{ui}
</MemoryRouter>,
renderOptions
),
}
}
// Example components for testing
// src/components/Navigation.tsx
import { useLocation, useNavigate } from 'react-router-dom'
const Navigation = () => {
const navigate = useNavigate()
const location = useLocation()
return (
<nav>
<button onClick={() => navigate('/home')}>Home</button>
<button onClick={() => navigate('/about')}>About</button>
<button onClick={() => navigate(-1)}>Back</button>
<div data-testid="location-display">
Current: {location.pathname}
</div>
</nav>
)
}
// src/components/Navigation.test.tsx
import { describe, it, expect } from 'vitest'
import { screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import Navigation from './Navigation'
describe('Navigation with MemoryRouter', () => {
// Test 1: Basic Navigation
it('renders at initial route', () => {
renderWithRouter(<Navigation />, {
initialEntries: ['/home']
})
expect(screen.getByTestId('location-display')).toHaveTextContent('/home')
})
// Test 2: Navigation History
it('maintains navigation history', async () => {
renderWithRouter(<Navigation />, {
initialEntries: ['/home', '/about', '/contact'],
initialIndex: 1 // Start at '/about'
})
expect(screen.getByTestId('location-display')).toHaveTextContent('/about')
// Click back button
await userEvent.click(screen.getByText('Back'))
expect(screen.getByTestId('location-display')).toHaveTextContent('/home')
})
// Test 3: Testing Routes with Parameters
it('handles route parameters', () => {
const ProductPage = () => {
const { id } = useParams<{ id: string }>()
return <div data-testid="product-id">Product: {id}</div>
}
render(
<MemoryRouter initialEntries={['/product/123']}>
<Routes>
<Route path="/product/:id" element={<ProductPage />} />
</Routes>
</MemoryRouter>
)
expect(screen.getByTestId('product-id')).toHaveTextContent('Product: 123')
})
// Test 4: Testing Query Parameters
it('handles query parameters', () => {
const SearchPage = () => {
const [searchParams] = useSearchParams()
return <div data-testid="search-query">
Query: {searchParams.get('q')}
</div>
}
render(
<MemoryRouter initialEntries={['/search?q=test-query']}>
<Routes>
<Route path="/search" element={<SearchPage />} />
</Routes>
</MemoryRouter>
)
expect(screen.getByTestId('search-query')).toHaveTextContent('Query: test-query')
})
// Test 5: Testing Protected Routes
it('redirects unauthorized access', () => {
const ProtectedRoute = () => {
const isAuthenticated = false
return isAuthenticated ? (
<div>Protected Content</div>
) : (
<Navigate to="/login" replace />
)
}
render(
<MemoryRouter initialEntries={['/dashboard']}>
<Routes>
<Route path="/dashboard" element={<ProtectedRoute />} />
<Route path="/login" element={<div>Login Page</div>} />
</Routes>
</MemoryRouter>
)
expect(screen.getByText('Login Page')).toBeInTheDocument()
})
// Test 6: Testing Nested Routes
it('handles nested routes', () => {
const Layout = () => (
<div>
<div>Header</div>
<Outlet />
</div>
)
const NestedPage = () => <div>Nested Content</div>
render(
<MemoryRouter initialEntries={['/parent/child']}>
<Routes>
<Route path="/parent" element={<Layout />}>
<Route path="child" element={<NestedPage />} />
</Route>
</Routes>
</MemoryRouter>
)
expect(screen.getByText('Header')).toBeInTheDocument()
expect(screen.getByText('Nested Content')).toBeInTheDocument()
})
// Test 7: Testing Route State
it('handles location state', () => {
const StateComponent = () => {
const location = useLocation()
return <div data-testid="state-display">
State: {location.state?.message}
</div>
}
render(
<MemoryRouter
initialEntries={[
{
pathname: '/state-test',
state: { message: 'Hello from state!' }
}
]}
>
<StateComponent />
</MemoryRouter>
)
expect(screen.getByTestId('state-display'))
.toHaveTextContent('State: Hello from state!')
})
// Test 8: Testing Multiple Navigation Steps
it('handles multiple navigation steps', async () => {
renderWithRouter(<Navigation />, {
initialEntries: ['/start'],
initialIndex: 0
})
// Navigate forward
await userEvent.click(screen.getByText('Home'))
expect(screen.getByTestId('location-display')).toHaveTextContent('/home')
await userEvent.click(screen.getByText('About'))
expect(screen.getByTestId('location-display')).toHaveTextContent('/about')
// Navigate backward twice
await userEvent.click(screen.getByText('Back'))
expect(screen.getByTestId('location-display')).toHaveTextContent('/home')
await userEvent.click(screen.getByText('Back'))
expect(screen.getByTestId('location-display')).toHaveTextContent('/start')
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment