Testing

Frontend Testing Guide

Testing Stack

  • Vitest for unit and integration tests
  • React Testing Library for component tests
  • Cypress for E2E testing
  • MSW for API mocking
  • Storybook for component development

Test Types

Unit Tests

import { renderHook } from '@testing-library/react-hooks'
import { useAuth } from './useAuth'

describe('useAuth', () => {
  it('should handle login', () => {
    const { result } = renderHook(() => useAuth())

    act(() => {
      result.current.login('test@example.com', 'password')
    })

    expect(result.current.isAuthenticated).toBe(true)
  })
})

Component Tests

import { render, screen, fireEvent } from '@testing-library/react'
import { ProjectCard } from './ProjectCard'

describe('ProjectCard', () => {
  it('should render project details', () => {
    render(
      <ProjectCard
        project={{
          name: 'Test Project',
          description: 'Test Description'
        }}
      />
    )

    expect(screen.getByText('Test Project')).toBeInTheDocument()
    expect(screen.getByText('Test Description')).toBeInTheDocument()
  })

  it('should handle click events', () => {
    const onEdit = vi.fn()

    render(
      <ProjectCard
        project={{
          name: 'Test Project'
        }}
        onEdit={onEdit}
      />
    )

    fireEvent.click(screen.getByRole('button', { name: /edit/i }))
    expect(onEdit).toHaveBeenCalled()
  })
})

Integration Tests

import { render, screen, waitFor } from '@testing-library/react'
import { ProjectList } from './ProjectList'
import { rest } from 'msw'
import { setupServer } from 'msw/node'

const server = setupServer(
  rest.get('/api/projects', (req, res, ctx) => {
    return res(
      ctx.json([
        { id: 1, name: 'Project 1' },
        { id: 2, name: 'Project 2' }
      ])
    )
  })
)

beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

test('loads and displays projects', async () => {
  render(<ProjectList />)

  await waitFor(() => {
    expect(screen.getByText('Project 1')).toBeInTheDocument())
    expect(screen.getByText('Project 2')).toBeInTheDocument())
  })
})

Test Organization

src/
├── components/
│   └── __tests__/      # Component tests
├── hooks/
│   └── __tests__/      # Hook tests
└── utils/
    └── __tests__/      # Utility tests

Best Practices

Writing Tests

  1. Test behavior, not implementation
  2. Use meaningful test descriptions
  3. Follow AAA pattern (Arrange, Act, Assert)
  4. Test edge cases and error states

Component Testing

  • Test user interactions
  • Verify rendered content
  • Check accessibility
  • Test loading states

Hook Testing

  • Test state updates
  • Verify side effects
  • Test error handling
  • Check cleanup

Running Tests

Development

# Run tests in watch mode
npm run test:watch

# Run with coverage
npm run test:coverage

CI Environment

# Run all tests
npm run test

# Run E2E tests
npm run test:e2e

Test Environment Setup

MSW Setup

// src/mocks/handlers.ts
import { rest } from 'msw'

export const handlers = [
  rest.get('/api/projects', (req, res, ctx) => {
    return res(
      ctx.json([
        { id: 1, name: 'Project 1' }
      ])
    )
  })
]

Test Utils

// src/test/utils.tsx
import { render } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

const queryClient = new QueryClient()

export function renderWithProviders(ui: React.ReactElement) {
  return render(
    <QueryClientProvider client={queryClient}>
      {ui}
    </QueryClientProvider>
  )
}