- Testing Framework: Jest 29
- React Testing: React Testing Library 16
- Environment: jsdom
- Coverage: Built-in Istanbul
# Run all tests
npm test
# Watch mode (recommended during development)
npm run test:watch
# Coverage report
npm run test:coverage
# Run specific test file
npm test ErrorBoundary.test.tsx
# Run tests matching pattern
npm test -- --testNamePattern="should render"__tests__/
βββ components/ # Component tests
β βββ ErrorBoundary.test.tsx
β βββ ProductCard.test.tsx
β βββ SearchBar.test.tsx
βββ lib/ # Utility tests
β βββ utils/
β β βββ cache.test.ts
β β βββ image.test.ts
β βββ seo/
β βββ metadata.test.ts
β βββ structured-data.test.ts
βββ integration/ # Integration tests
βββ product-flow.test.tsx
βββ favorites-flow.test.tsx
Example: Testing ErrorBoundary
import { render, screen, fireEvent } from '@testing-library/react'
import '@testing-library/jest-dom'
import { ErrorBoundary } from '@/components/ErrorBoundary'
describe('ErrorBoundary', () => {
it('should render children when no error', () => {
render(
<ErrorBoundary>
<div>Content</div>
</ErrorBoundary>
)
expect(screen.getByText('Content')).toBeInTheDocument()
})
it('should show fallback UI on error', () => {
const ThrowError = () => {
throw new Error('Test error')
}
render(
<ErrorBoundary>
<ThrowError />
</ErrorBoundary>
)
expect(screen.getByText(/something went wrong/i)).toBeInTheDocument()
})
})Example: Testing cache utilities
import { cache } from '@/lib/utils/cache'
describe('Cache', () => {
beforeEach(() => {
cache.clear()
})
it('should store and retrieve values', () => {
cache.set('key', 'value')
expect(cache.get('key')).toBe('value')
})
it('should expire after TTL', async () => {
cache.set('key', 'value', 1) // 1 second
await new Promise(resolve => setTimeout(resolve, 1100))
expect(cache.get('key')).toBeNull()
})
})Example: Testing user flows
describe('Product Discovery Flow', () => {
it('should search, filter, and view product', async () => {
// 1. Render search page
// 2. Enter search query
// 3. Submit search
// 4. Verify results
// 5. Click product
// 6. Verify product page
})
})it('should add item to favorites', () => {
// Arrange: Set up test data
const product = { id: '1', title: 'Test' }
// Act: Perform action
render(<ProductCard product={product} />)
const button = screen.getByRole('button', { name: /favorite/i })
fireEvent.click(button)
// Assert: Verify outcome
expect(button).toHaveClass('favorited')
})β DO: Test from user perspective
screen.getByRole('button', { name: /add to cart/i })β DON'T: Test implementation details
screen.getByClassName('add-to-cart-button')Every test should verify accessibility:
it('should have accessible button', () => {
render(<MyComponent />)
// Verify role
const button = screen.getByRole('button')
expect(button).toBeInTheDocument()
// Verify label
expect(button).toHaveAccessibleName('Submit')
// Verify keyboard navigation
fireEvent.keyDown(button, { key: 'Enter' })
expect(mockSubmit).toHaveBeenCalled()
})it('should load products', async () => {
render(<ProductList />)
// Wait for loading to finish
await waitFor(() => {
expect(screen.queryByText(/loading/i)).not.toBeInTheDocument()
})
// Verify products displayed
expect(screen.getByText('Product 1')).toBeInTheDocument()
})Mock API calls:
jest.mock('@/lib/ebay-api', () => ({
searchEbayProducts: jest.fn(() => Promise.resolve(mockProducts))
}))Mock context:
const mockToast = jest.fn()
jest.mock('@/contexts/ToastContext', () => ({
useToast: () => ({ showToast: mockToast })
}))| Type | Coverage | Target | Status |
|---|---|---|---|
| Overall | 65% | 80% | π‘ Good |
| Components | 60% | 80% | π‘ Good |
| Utilities | 85% | 90% | β Excellent |
| Integration | 40% | 60% | π‘ Good |
// jest.config.js
coverageThresholds: {
global: {
statements: 65,
branches: 60,
functions: 65,
lines: 65
}
}it('should submit form with valid data', async () => {
const onSubmit = jest.fn()
render(<ContactForm onSubmit={onSubmit} />)
// Fill form
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: 'test@example.com' }
})
// Submit
fireEvent.click(screen.getByRole('button', { name: /submit/i }))
// Verify
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
email: 'test@example.com'
})
})
})const wrapper = ({ children }: { children: React.ReactNode }) => (
<FavoritesProvider>
<ToastProvider>
{children}
</ToastProvider>
</FavoritesProvider>
)
it('should use context', () => {
render(<MyComponent />, { wrapper })
// Test component that uses context
})import { useRouter } from 'next/navigation'
jest.mock('next/navigation', () => ({
useRouter: jest.fn()
}))
it('should navigate on click', () => {
const push = jest.fn()
;(useRouter as jest.Mock).mockReturnValue({ push })
render(<MyComponent />)
fireEvent.click(screen.getByText(/go to product/i))
expect(push).toHaveBeenCalledWith('/product/123')
})name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '20'
- run: npm ci
- run: npm test
- run: npm run test:coverageLast Updated: February 16, 2026
Test Coverage: 65%+ β
Tests Passing: 100% β