# Testing Strategy: [Project Name] **Testing Framework**: Vitest (unit/integration) **E2E Framework**: Playwright (optional) **Coverage Target**: 70%+ for critical paths **Last Updated**: [Date] --- ## Overview This document defines the testing strategy including what to test, how to test, and when to test. **Testing Philosophy**: - **Test behavior, not implementation** - Focus on user-facing functionality - **Test critical paths first** - Auth, data CRUD, payments, etc - **Fast feedback** - Unit tests run in milliseconds - **Realistic tests** - Integration tests use real-ish data - **E2E for happy paths** - Cover main user workflows **Testing Pyramid**: ``` ┌──────────┐ │ E2E │ ← Few (slow, brittle) └──────────┘ ┌──────────────┐ │ Integration │ ← Some (medium speed) └──────────────┘ ┌───────────────────┐ │ Unit Tests │ ← Many (fast, focused) └───────────────────┘ ``` --- ## What to Test ### ✅ Do Test **Business Logic**: - Data validation (Zod schemas) - Data transformations - Complex calculations - State management logic **API Endpoints**: - All HTTP methods (GET, POST, PATCH, DELETE) - All response codes (200, 400, 401, 404, 500) - Request validation - Authorization checks **User Workflows**: - Authentication flow - CRUD operations (create, read, update, delete) - Form submissions - Error handling **Critical Paths**: - Payment processing (if applicable) - Data export/import - Email notifications - File uploads --- ### ❌ Don't Test **Third-party libraries**: Trust that React, Tailwind, shadcn/ui work **Implementation details**: Internal function calls, component props (unless part of public API) **Trivial code**: Simple getters/setters, pass-through functions **UI styling**: Visual regression testing is overkill for most projects --- ## Testing Layers ### 1. Unit Tests (Many) **Purpose**: Test individual functions and utilities in isolation **Tool**: Vitest **What to test**: - Utility functions (`src/lib/utils.ts`) - Zod schemas (`src/lib/schemas.ts`) - Data transformations - Pure functions **Example**: ```typescript // src/lib/utils.test.ts import { describe, it, expect } from 'vitest' import { formatDate, cn } from './utils' describe('formatDate', () => { it('formats Unix timestamp to readable date', () => { expect(formatDate(1234567890)).toBe('Feb 14, 2009') }) it('handles invalid timestamps', () => { expect(formatDate(-1)).toBe('Invalid Date') }) }) describe('cn', () => { it('merges class names', () => { expect(cn('foo', 'bar')).toBe('foo bar') }) it('handles conditional classes', () => { expect(cn('foo', false && 'bar', 'baz')).toBe('foo baz') }) }) ``` **Run**: `npm run test` --- ### 2. Integration Tests (Some) **Purpose**: Test API endpoints with real database interactions **Tool**: Vitest + Miniflare (Cloudflare Workers simulator) **What to test**: - API routes (`/api/*`) - Middleware (auth, CORS, error handling) - Database operations (CRUD) **Setup**: ```typescript // tests/setup.ts import { beforeAll, afterAll, beforeEach } from 'vitest' import { env, createExecutionContext, waitOnExecutionContext } from 'cloudflare:test' beforeAll(async () => { // Setup test database await env.DB.exec('CREATE TABLE IF NOT EXISTS users (...)') }) beforeEach(async () => { // Clear database before each test await env.DB.exec('DELETE FROM users') }) afterAll(async () => { // Cleanup }) ``` **Example Test**: ```typescript // tests/api/tasks.test.ts import { describe, it, expect } from 'vitest' import app from '../../src/index' describe('POST /api/tasks', () => { it('creates a task for authenticated user', async () => { const res = await app.request('/api/tasks', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer valid_jwt_token' }, body: JSON.stringify({ title: 'Test Task', description: 'Test Description' }) }) expect(res.status).toBe(201) const data = await res.json() expect(data.title).toBe('Test Task') }) it('returns 401 without auth', async () => { const res = await app.request('/api/tasks', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: 'Test' }) }) expect(res.status).toBe(401) }) it('returns 400 with invalid data', async () => { const res = await app.request('/api/tasks', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer valid_jwt_token' }, body: JSON.stringify({ title: '' // invalid: empty title }) }) expect(res.status).toBe(400) }) }) ``` **Run**: `npm run test` --- ### 3. E2E Tests (Few) - Optional **Purpose**: Test complete user workflows in real browser **Tool**: Playwright **What to test**: - Authentication flow (sign up, sign in, sign out) - Main CRUD workflows - Critical paths (payments, exports, etc) **Setup**: ```bash npm install -D @playwright/test npx playwright install ``` **Example Test**: ```typescript // e2e/auth.spec.ts import { test, expect } from '@playwright/test' test('user can sign in and create a task', async ({ page }) => { // Sign in await page.goto('http://localhost:5173') await page.click('text=Sign In') await page.fill('input[name="email"]', 'test@example.com') await page.fill('input[name="password"]', 'password123') await page.click('button[type="submit"]') // Wait for redirect to dashboard await expect(page).toHaveURL(/\/dashboard/) // Create task await page.click('text=Create Task') await page.fill('input[name="title"]', 'New Task') await page.fill('textarea[name="description"]', 'Task description') await page.click('button:has-text("Save")') // Verify task appears await expect(page.locator('text=New Task')).toBeVisible() }) ``` **Run**: `npm run test:e2e` --- ## Test Coverage **Target Coverage**: 70%+ for critical code **What to cover**: - ✅ All API routes - ✅ All middleware - ✅ Business logic and utilities - ✅ Zod schemas (validation tests) - ⚠️ React components (optional - prefer E2E for UI) **Generate Coverage Report**: ```bash npm run test -- --coverage ``` **View Report**: `coverage/index.html` --- ## Testing Checklist Before marking a phase complete, verify: ### API Phase - [ ] All endpoints return correct status codes (200, 400, 401, 404, 500) - [ ] Request validation works (invalid data → 400) - [ ] Authorization works (no token → 401, wrong user → 403) - [ ] Database operations succeed - [ ] Error handling catches exceptions ### UI Phase - [ ] Forms validate input (Zod schemas) - [ ] Forms show error messages - [ ] Loading states display correctly - [ ] Error states display correctly - [ ] Happy path works (create, read, update, delete) ### Integration Phase - [ ] Third-party service integration works - [ ] Webhooks fire correctly - [ ] Error handling for external failures --- ## Manual Testing **Automated tests don't catch everything**. Manually test: ### Before Each Deployment - [ ] Sign in works - [ ] Main workflows work (create, edit, delete) - [ ] Forms validate correctly - [ ] Errors display properly - [ ] Dark/light mode works - [ ] Mobile layout works ### Smoke Test Checklist ``` 1. Visit homepage → Should load 2. Click Sign In → Should show Clerk modal 3. Sign in → Should redirect to dashboard 4. Create [resource] → Should appear in list 5. Edit [resource] → Changes should save 6. Delete [resource] → Should remove from list 7. Sign out → Should return to homepage ``` --- ## Continuous Integration (CI) **GitHub Actions** (optional): ```yaml # .github/workflows/test.yml 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: 18 - run: npm install - run: npm run test - run: npm run build ``` **Run tests on**: - Every push - Every pull request - Before deployment --- ## Test Data ### Seed Data for Testing Create `migrations/seed.sql` with realistic test data: ```sql INSERT INTO users (email, clerk_id, display_name, created_at, updated_at) VALUES ('alice@example.com', 'clerk_alice', 'Alice', strftime('%s', 'now'), strftime('%s', 'now')), ('bob@example.com', 'clerk_bob', 'Bob', strftime('%s', 'now'), strftime('%s', 'now')); INSERT INTO tasks (user_id, title, description, created_at, updated_at) VALUES (1, 'Review PR', 'Review the new feature PR', strftime('%s', 'now'), strftime('%s', 'now')), (1, 'Write tests', 'Add tests for API endpoints', strftime('%s', 'now'), strftime('%s', 'now')), (2, 'Deploy app', 'Deploy to production', strftime('%s', 'now'), strftime('%s', 'now')); ``` **Load seed data**: ```bash npx wrangler d1 execute [DB_NAME] --local --file=migrations/seed.sql ``` --- ## Testing Tools ### Installed - **Vitest**: Unit and integration tests - **@cloudflare/vitest-pool-workers**: Test Workers in Miniflare - **@vitest/coverage-v8**: Code coverage ### Optional - **Playwright**: E2E browser testing - **@testing-library/react**: React component testing (if needed) - **MSW**: Mock Service Worker (mock external APIs) --- ## Test Scripts **package.json**: ```json { "scripts": { "test": "vitest", "test:ui": "vitest --ui", "test:coverage": "vitest --coverage", "test:e2e": "playwright test" } } ``` **Run tests**: ```bash npm run test # Run all tests (watch mode) npm run test:coverage # Generate coverage report npm run test:e2e # Run E2E tests (Playwright) ``` --- ## Debugging Tests **Vitest UI** (visual test runner): ```bash npm run test:ui ``` **Debug single test**: ```typescript it.only('specific test', () => { // Only this test runs }) ``` **Console logs in tests**: They appear in terminal output **Playwright debug mode**: ```bash npx playwright test --debug ``` --- ## Testing Best Practices ### ✅ Do - **Arrange, Act, Assert** - Structure tests clearly - **One assertion per test** (when possible) - **Descriptive test names** - "should return 401 when token is missing" - **Test edge cases** - Empty strings, null values, large numbers - **Use realistic data** - Test with production-like data ### ❌ Don't - **Test implementation details** - Internal state, private methods - **Over-mock** - Use real database in integration tests - **Brittle selectors** - Avoid testing specific CSS classes - **Flaky tests** - Fix immediately or remove - **Slow tests** - Optimize or move to E2E --- ## Test-Driven Development (TDD) - Optional **For critical features**, consider writing tests first: 1. Write failing test 2. Implement feature (test passes) 3. Refactor 4. Repeat **Benefits**: Better design, fewer bugs, built-in documentation **When to use**: Complex business logic, critical paths, bug fixes --- ## Monitoring Test Health **Metrics to track**: - Test pass rate (should be 100%) - Test coverage (target 70%+) - Test execution time (keep fast) - Flaky test count (should be 0) **Weekly review**: - Are tests passing? - Is coverage dropping? - Are tests slowing down? - Any flaky tests to fix? --- ## Future Testing Enhancements - [ ] Add visual regression testing (if needed) - [ ] Add performance testing (if needed) - [ ] Add accessibility testing (axe-core) - [ ] Add API contract testing (Pact) --- ## Revision History **v1.0** ([Date]): Initial testing strategy **v1.1** ([Date]): [Changes made]