Docs /frontend/testing-frontend

Frontend Testing

Testing React components, hooks, and user interactions

๐Ÿ“‹ Overview

This guide covers frontend-specific testing with Vitest, Testing Library, and MSW.

๐Ÿ› ๏ธ Setup

Install Dependencies

cd apps/web
pnpm add -D vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event @vitejs/plugin-react jsdom msw

Vitest Config

File: apps/web/vitest.config.ts

import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'

export default defineConfig({
  plugins: [react(), tsconfigPaths()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './test/setup.ts',
  },
})

Test Setup

File: apps/web/test/setup.ts

import '@testing-library/jest-dom/vitest'
import { cleanup } from '@testing-library/react'
import { afterEach } from 'vitest'

afterEach(() => {
  cleanup()
})

๐Ÿงช Component Testing

Basic Component Test

import { render, screen } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import { Button } from '@/components/ui/button'

describe('Button', () => {
  it('renders with text', () => {
    render(<Button>Click me</Button>)
    expect(screen.getByText('Click me')).toBeInTheDocument()
  })

  it('calls onClick when clicked', async () => {
    const handleClick = vi.fn()
    const user = userEvent.setup()
    
    render(<Button onClick={handleClick}>Click</Button>)
    await user.click(screen.getByText('Click'))
    
    expect(handleClick).toHaveBeenCalledTimes(1)
  })
})

๐Ÿ”Œ Testing with tRPC

Mock tRPC Responses

import { setupServer } from 'msw/node'
import { http, HttpResponse } from 'msw'

const server = setupServer(
  http.post('http://localhost:4000/trpc/projects.list', () => {
    return HttpResponse.json({
      result: { data: [{ id: '1', name: 'Project 1' }] },
    })
  })
)

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

Test Component with tRPC

import { render, screen, waitFor } from '@testing-library/react'
import { ProjectList } from '@/components/projects/project-list'
import { createWrapper } from '@/test/utils'

test('renders projects', async () => {
  render(<ProjectList />, { wrapper: createWrapper() })
  
  await waitFor(() => {
    expect(screen.getByText('Project 1')).toBeInTheDocument()
  })
})

๐ŸŽฃ Testing Hooks

import { renderHook, waitFor } from '@testing-library/react'
import { useProjects } from '@/lib/hooks/use-projects'

test('useProjects fetches projects', async () => {
  const { result } = renderHook(() => useProjects(), {
    wrapper: createWrapper(),
  })

  await waitFor(() => {
    expect(result.current.projects).toHaveLength(2)
  })
})

๐Ÿ“š Run Tests

# Run all tests
pnpm test

# Watch mode
pnpm test:watch

# Coverage
pnpm test:coverage

# UI mode
pnpm test:ui

๐Ÿ”— Related Documentation


Section 04 (Frontend) complete! Next: Backend documentation

Reading this with an agent? /docs/frontend/testing-frontend.md serves the raw markdown.

All docs