Skip to content

Latest commit

Β 

History

History
352 lines (265 loc) Β· 6.92 KB

File metadata and controls

352 lines (265 loc) Β· 6.92 KB

Testing Guide

πŸ§ͺ Testing Infrastructure

Tech Stack

  • Testing Framework: Jest 29
  • React Testing: React Testing Library 16
  • Environment: jsdom
  • Coverage: Built-in Istanbul

πŸš€ Getting Started

Running Tests

# 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"

Test Structure

__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

βœ… Writing Tests

Component Tests

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()
  })
})

Utility Tests

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()
  })
})

Integration Tests

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
  })
})

🎯 Testing Best Practices

1. Arrange-Act-Assert (AAA)

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')
})

2. User-Centric Testing

βœ… DO: Test from user perspective

screen.getByRole('button', { name: /add to cart/i })

❌ DON'T: Test implementation details

screen.getByClassName('add-to-cart-button')

3. Accessibility Testing

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()
})

4. Testing Async Operations

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()
})

5. Mocking

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 })
}))

πŸ“Š Coverage Goals

Current Coverage

Type Coverage Target Status
Overall 65% 80% 🟑 Good
Components 60% 80% 🟑 Good
Utilities 85% 90% βœ… Excellent
Integration 40% 60% 🟑 Good

Coverage Thresholds

// jest.config.js
coverageThresholds: {
  global: {
    statements: 65,
    branches: 60,
    functions: 65,
    lines: 65
  }
}

πŸ”§ Common Testing Patterns

Testing Forms

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'
    })
  })
})

Testing Context Providers

const wrapper = ({ children }: { children: React.ReactNode }) => (
  <FavoritesProvider>
    <ToastProvider>
      {children}
    </ToastProvider>
  </FavoritesProvider>
)

it('should use context', () => {
  render(<MyComponent />, { wrapper })
  // Test component that uses context
})

Testing Navigation

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')
})

πŸš€ Continuous Integration

GitHub Actions

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:coverage

πŸ“š Resources


Last Updated: February 16, 2026
Test Coverage: 65%+ βœ…
Tests Passing: 100% βœ