Skip to content

Latest commit

 

History

History
1165 lines (954 loc) · 31.7 KB

File metadata and controls

1165 lines (954 loc) · 31.7 KB

QA & Testing Guide

Overview

Comprehensive testing strategy based on production experience with forms-service, covering unit tests, integration tests, E2E tests, accessibility testing, and performance testing.

Table of Contents

  1. Testing Philosophy
  2. Testing Stack
  3. Test Architecture
  4. Unit Testing
  5. Integration Testing
  6. End-to-End Testing
  7. Accessibility Testing
  8. Performance Testing
  9. API Testing
  10. Production Insights

Testing Philosophy

Test Pyramid Strategy

        /\
       /  \
      /E2E \     <- Few, High-level, Slow
     /______\
    /        \
   /Integration\ <- Some, Medium-level, Medium
  /__________\
 /            \
/    Unit      \  <- Many, Low-level, Fast
/_______________\

Key Principles

  1. Fast Feedback: Unit tests run in seconds, not minutes
  2. Reliability: Tests should be deterministic and stable
  3. Maintainability: Tests should be easy to read and update
  4. Coverage: Focus on business logic and critical paths
  5. Automation: All tests should run automatically in CI/CD

Testing Stack

Frontend Testing

  • Unit Testing: Jest + React Testing Library
  • Component Testing: Jest + React Testing Library
  • E2E Testing: Playwright
  • Accessibility Testing: axe-core + jest-axe
  • Performance Testing: Lighthouse CI
  • Visual Testing: Playwright screenshots

Backend Testing

  • Unit Testing: pytest + pytest-asyncio
  • Integration Testing: pytest + test database
  • API Testing: httpx + pytest-httpx
  • Load Testing: locust
  • Security Testing: bandit + safety

Test Tools & Utilities

  • Test Data: Factory Boy (Python) / MSW (JavaScript)
  • Coverage: pytest-cov (Python) / Jest coverage (JavaScript)
  • Reporting: pytest-html / Jest HTML reporter
  • CI/CD: GitHub Actions

Test Architecture

Project Structure

tests/
├── unit/                    # Unit tests
│   ├── frontend/
│   │   ├── components/
│   │   ├── hooks/
│   │   └── utils/
│   └── backend/
│       ├── api/
│       ├── services/
│       └── utils/
├── integration/             # Integration tests
│   ├── api/
│   ├── database/
│   └── services/
├── e2e/                     # End-to-end tests
│   ├── user-flows/
│   ├── forms/
│   └── accessibility/
├── performance/             # Performance tests
│   ├── load-tests/
│   └── benchmarks/
├── fixtures/                # Test data and mocks
│   ├── api-responses/
│   ├── test-data/
│   └── mocks/
└── utils/                   # Test utilities
    ├── test-helpers.ts
    └── setup.ts

Configuration Files

// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
  testMatch: [
    '<rootDir>/tests/**/*.test.{ts,tsx}',
    '<rootDir>/src/**/*.test.{ts,tsx}'
  ],
  collectCoverageFrom: [
    'src/**/*.{ts,tsx}',
    '!src/**/*.d.ts',
    '!src/**/*.stories.{ts,tsx}',
    '!src/types/**/*'
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  },
  moduleNameMapping: {
    '^@/(.*)$': '<rootDir>/src/$1'
  }
};

Unit Testing

1. Component Testing

// tests/unit/components/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from '@/components/ui/Button';

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

  it('applies variant classes correctly', () => {
    render(<Button variant="secondary">Secondary</Button>);
    const button = screen.getByRole('button');
    expect(button).toHaveClass('bg-gray-200');
  });

  it('handles loading state', () => {
    render(<Button isLoading>Loading</Button>);
    expect(screen.getByText('Loading...')).toBeInTheDocument();
    expect(screen.getByRole('button')).toBeDisabled();
  });

  it('calls onClick handler', () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click me</Button>);
    
    fireEvent.click(screen.getByRole('button'));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  it('supports keyboard navigation', () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click me</Button>);
    
    const button = screen.getByRole('button');
    button.focus();
    expect(button).toHaveFocus();
    
    fireEvent.keyDown(button, { key: 'Enter' });
    expect(handleClick).toHaveBeenCalledTimes(1);
  });
});

2. Hook Testing

// tests/unit/hooks/useApi.test.tsx
import { renderHook, waitFor } from '@testing-library/react';
import { useApi } from '@/hooks/useApi';

const mockApiFunction = jest.fn();

describe('useApi Hook', () => {
  beforeEach(() => {
    mockApiFunction.mockClear();
  });

  it('should handle successful API call', async () => {
    const mockData = { id: 1, name: 'Test' };
    mockApiFunction.mockResolvedValue(mockData);

    const { result } = renderHook(() => useApi(mockApiFunction));

    expect(result.current.loading).toBe(false);
    expect(result.current.data).toBe(null);
    expect(result.current.error).toBe(null);

    await waitFor(() => result.current.execute());

    expect(result.current.loading).toBe(false);
    expect(result.current.data).toEqual(mockData);
    expect(result.current.error).toBe(null);
  });

  it('should handle API error', async () => {
    const mockError = new Error('API Error');
    mockApiFunction.mockRejectedValue(mockError);

    const { result } = renderHook(() => useApi(mockApiFunction));

    await waitFor(() => result.current.execute());

    expect(result.current.loading).toBe(false);
    expect(result.current.data).toBe(null);
    expect(result.current.error).toEqual(mockError);
  });

  it('should handle loading state', async () => {
    mockApiFunction.mockImplementation(() => 
      new Promise(resolve => setTimeout(() => resolve({}), 100))
    );

    const { result } = renderHook(() => useApi(mockApiFunction));

    const promise = result.current.execute();
    expect(result.current.loading).toBe(true);

    await waitFor(() => promise);
    expect(result.current.loading).toBe(false);
  });
});

3. Utility Function Testing

// tests/unit/utils/validation.test.ts
import { validateEmail, validatePassword } from '@/utils/validation';

describe('Validation Utils', () => {
  describe('validateEmail', () => {
    it('should validate correct email formats', () => {
      const validEmails = [
        'test@example.com',
        'user.name@domain.co.uk',
        'user+tag@example.org'
      ];

      validEmails.forEach(email => {
        expect(validateEmail(email)).toBe(true);
      });
    });

    it('should reject invalid email formats', () => {
      const invalidEmails = [
        'invalid-email',
        '@domain.com',
        'user@',
        'user.domain.com'
      ];

      invalidEmails.forEach(email => {
        expect(validateEmail(email)).toBe(false);
      });
    });
  });

  describe('validatePassword', () => {
    it('should validate strong passwords', () => {
      const strongPasswords = [
        'StrongPass123!',
        'MySecure@Password2024',
        'Complex#Pass1'
      ];

      strongPasswords.forEach(password => {
        expect(validatePassword(password)).toBe(true);
      });
    });

    it('should reject weak passwords', () => {
      const weakPasswords = [
        'weak',
        'password123',
        'PASSWORD',
        'NoNumbers!'
      ];

      weakPasswords.forEach(password => {
        expect(validatePassword(password)).toBe(false);
      });
    });
  });
});

Integration Testing

1. API Integration Testing

// tests/integration/api/auth.test.ts
import { TestClient } from '@/tests/utils/test-client';
import { testDb } from '@/tests/utils/test-db';

describe('Authentication API', () => {
  let client: TestClient;

  beforeEach(async () => {
    client = new TestClient();
    await testDb.clear();
  });

  afterEach(async () => {
    await client.close();
  });

  it('should register new user', async () => {
    const userData = {
      email: 'test@example.com',
      password: 'SecurePass123!',
      firstName: 'John',
      lastName: 'Doe'
    };

    const response = await client.post('/api/auth/register', userData);

    expect(response.status).toBe(201);
    expect(response.body.success).toBe(true);
    expect(response.body.data.user.email).toBe(userData.email);
    expect(response.body.data.user.password).toBeUndefined();
  });

  it('should login existing user', async () => {
    // Create user first
    await client.post('/api/auth/register', {
      email: 'test@example.com',
      password: 'SecurePass123!',
      firstName: 'John',
      lastName: 'Doe'
    });

    const response = await client.post('/api/auth/login', {
      email: 'test@example.com',
      password: 'SecurePass123!'
    });

    expect(response.status).toBe(200);
    expect(response.body.success).toBe(true);
    expect(response.body.data.token).toBeDefined();
  });

  it('should handle invalid credentials', async () => {
    const response = await client.post('/api/auth/login', {
      email: 'nonexistent@example.com',
      password: 'wrongpassword'
    });

    expect(response.status).toBe(401);
    expect(response.body.success).toBe(false);
    expect(response.body.message).toContain('Invalid credentials');
  });
});

2. Database Integration Testing

# tests/integration/test_database.py
import pytest
from app.models import User, Content
from app.core.database import SessionLocal

@pytest.fixture
def db_session():
    session = SessionLocal()
    yield session
    session.close()

def test_create_user(db_session):
    """Test user creation and retrieval"""
    user_data = {
        'email': 'test@example.com',
        'first_name': 'John',
        'last_name': 'Doe',
        'password_hash': 'hashed_password'
    }
    
    user = User(**user_data)
    db_session.add(user)
    db_session.commit()
    
    retrieved_user = db_session.query(User).filter(User.email == 'test@example.com').first()
    assert retrieved_user is not None
    assert retrieved_user.first_name == 'John'
    assert retrieved_user.last_name == 'Doe'

def test_user_content_relationship(db_session):
    """Test user-content relationship"""
    user = User(
        email='author@example.com',
        first_name='Author',
        last_name='Name',
        password_hash='hashed'
    )
    db_session.add(user)
    db_session.commit()
    
    content = Content(
        title='Test Content',
        body='Test content body',
        author_id=user.id
    )
    db_session.add(content)
    db_session.commit()
    
    assert len(user.contents) == 1
    assert user.contents[0].title == 'Test Content'

End-to-End Testing

1. Playwright Configuration

// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests/e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: [
    ['html'],
    ['json', { outputFile: 'test-results/results.json' }],
    ['junit', { outputFile: 'test-results/results.xml' }]
  ],
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure'
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 5'] },
    },
    {
      name: 'Mobile Safari',
      use: { ...devices['iPhone 12'] },
    },
  ],
  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
});

2. User Flow Testing

// tests/e2e/user-flows/signup-flow.spec.ts
import { test, expect } from '@playwright/test';

test.describe('User Signup Flow', () => {
  test('should complete full signup process', async ({ page }) => {
    await page.goto('/signup');

    // Fill out form
    await page.fill('[data-testid="firstName"]', 'John');
    await page.fill('[data-testid="lastName"]', 'Doe');
    await page.fill('[data-testid="email"]', 'john.doe@example.com');
    await page.fill('[data-testid="password"]', 'SecurePass123!');
    await page.fill('[data-testid="confirmPassword"]', 'SecurePass123!');
    await page.check('[data-testid="terms"]');

    // Submit form
    await page.click('[data-testid="submit-button"]');

    // Wait for navigation
    await page.waitForURL('/dashboard');

    // Verify user is logged in
    await expect(page.getByText('Welcome, John!')).toBeVisible();
  });

  test('should show validation errors for invalid input', async ({ page }) => {
    await page.goto('/signup');

    // Try to submit empty form
    await page.click('[data-testid="submit-button"]');

    // Check for validation errors
    await expect(page.getByText('First name is required')).toBeVisible();
    await expect(page.getByText('Email is required')).toBeVisible();
    await expect(page.getByText('Password is required')).toBeVisible();
  });

  test('should handle server errors gracefully', async ({ page }) => {
    // Mock server error
    await page.route('**/api/auth/register', (route) => {
      route.fulfill({
        status: 500,
        contentType: 'application/json',
        body: JSON.stringify({
          success: false,
          message: 'Server error'
        })
      });
    });

    await page.goto('/signup');

    // Fill out form
    await page.fill('[data-testid="firstName"]', 'John');
    await page.fill('[data-testid="lastName"]', 'Doe');
    await page.fill('[data-testid="email"]', 'john.doe@example.com');
    await page.fill('[data-testid="password"]', 'SecurePass123!');
    await page.fill('[data-testid="confirmPassword"]', 'SecurePass123!');
    await page.check('[data-testid="terms"]');

    // Submit form
    await page.click('[data-testid="submit-button"]');

    // Check for error message
    await expect(page.getByText('Server error')).toBeVisible();
  });
});

3. Form Testing

// tests/e2e/forms/contact-form.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Contact Form', () => {
  test('should submit contact form successfully', async ({ page }) => {
    await page.goto('/contact');

    // Fill out form
    await page.fill('[name="name"]', 'John Doe');
    await page.fill('[name="email"]', 'john@example.com');
    await page.fill('[name="subject"]', 'Test Subject');
    await page.fill('[name="message"]', 'This is a test message');

    // Submit form
    await page.click('[type="submit"]');

    // Verify success message
    await expect(page.getByText('Thank you for your message!')).toBeVisible();
  });

  test('should validate required fields', async ({ page }) => {
    await page.goto('/contact');

    // Submit empty form
    await page.click('[type="submit"]');

    // Check validation errors
    await expect(page.getByText('Name is required')).toBeVisible();
    await expect(page.getByText('Email is required')).toBeVisible();
    await expect(page.getByText('Message is required')).toBeVisible();
  });

  test('should validate email format', async ({ page }) => {
    await page.goto('/contact');

    await page.fill('[name="email"]', 'invalid-email');
    await page.click('[type="submit"]');

    await expect(page.getByText('Please enter a valid email address')).toBeVisible();
  });
});

Accessibility Testing

1. Automated Accessibility Testing

// tests/accessibility/a11y.test.ts
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test.describe('Accessibility Tests', () => {
  test('homepage should be accessible', async ({ page }) => {
    await page.goto('/');
    
    const accessibilityScanResults = await new AxeBuilder({ page })
      .withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
      .analyze();
    
    expect(accessibilityScanResults.violations).toEqual([]);
  });

  test('signup form should be accessible', async ({ page }) => {
    await page.goto('/signup');
    
    const accessibilityScanResults = await new AxeBuilder({ page })
      .withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
      .analyze();
    
    expect(accessibilityScanResults.violations).toEqual([]);
  });

  test('dashboard should be accessible', async ({ page }) => {
    // Login first
    await page.goto('/login');
    await page.fill('[name="email"]', 'test@example.com');
    await page.fill('[name="password"]', 'password');
    await page.click('[type="submit"]');
    
    await page.waitForURL('/dashboard');
    
    const accessibilityScanResults = await new AxeBuilder({ page })
      .withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
      .analyze();
    
    expect(accessibilityScanResults.violations).toEqual([]);
  });
});

2. Keyboard Navigation Testing

// tests/accessibility/keyboard-navigation.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Keyboard Navigation', () => {
  test('should navigate through signup form with keyboard', async ({ page }) => {
    await page.goto('/signup');

    // Start navigation
    await page.keyboard.press('Tab');
    await expect(page.locator('[data-testid="firstName"]')).toBeFocused();

    await page.keyboard.press('Tab');
    await expect(page.locator('[data-testid="lastName"]')).toBeFocused();

    await page.keyboard.press('Tab');
    await expect(page.locator('[data-testid="email"]')).toBeFocused();

    await page.keyboard.press('Tab');
    await expect(page.locator('[data-testid="password"]')).toBeFocused();

    await page.keyboard.press('Tab');
    await expect(page.locator('[data-testid="confirmPassword"]')).toBeFocused();

    await page.keyboard.press('Tab');
    await expect(page.locator('[data-testid="terms"]')).toBeFocused();

    await page.keyboard.press('Tab');
    await expect(page.locator('[data-testid="submit-button"]')).toBeFocused();
  });

  test('should submit form with Enter key', async ({ page }) => {
    await page.goto('/signup');

    // Fill form
    await page.fill('[data-testid="firstName"]', 'John');
    await page.fill('[data-testid="lastName"]', 'Doe');
    await page.fill('[data-testid="email"]', 'john@example.com');
    await page.fill('[data-testid="password"]', 'SecurePass123!');
    await page.fill('[data-testid="confirmPassword"]', 'SecurePass123!');
    await page.check('[data-testid="terms"]');

    // Focus submit button and press Enter
    await page.focus('[data-testid="submit-button"]');
    await page.keyboard.press('Enter');

    // Verify form submission
    await expect(page).toHaveURL('/dashboard');
  });
});

3. Screen Reader Testing

// tests/accessibility/screen-reader.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Screen Reader Support', () => {
  test('should have proper ARIA labels', async ({ page }) => {
    await page.goto('/signup');

    // Check form has proper role
    const form = page.locator('form');
    await expect(form).toHaveAttribute('role', 'form');

    // Check required fields have proper labels
    const firstNameField = page.locator('[data-testid="firstName"]');
    await expect(firstNameField).toHaveAttribute('aria-required', 'true');

    // Check error messages are announced
    await page.click('[data-testid="submit-button"]');
    const errorMessage = page.locator('[role="alert"]').first();
    await expect(errorMessage).toBeVisible();
  });

  test('should announce form validation errors', async ({ page }) => {
    await page.goto('/signup');

    // Submit empty form
    await page.click('[data-testid="submit-button"]');

    // Check error regions
    const errorRegions = page.locator('[role="alert"]');
    await expect(errorRegions).toHaveCount(4); // firstName, lastName, email, password
  });
});

Performance Testing

1. Load Testing

# tests/performance/load_test.py
from locust import HttpUser, task, between
import json

class WebsiteUser(HttpUser):
    wait_time = between(1, 3)
    
    def on_start(self):
        """Login before running tasks"""
        self.login()
    
    def login(self):
        """Login user"""
        response = self.client.post("/api/auth/login", json={
            "email": "test@example.com",
            "password": "password"
        })
        if response.status_code == 200:
            self.token = response.json()["data"]["token"]
            self.client.headers.update({"Authorization": f"Bearer {self.token}"})
    
    @task(3)
    def view_homepage(self):
        """View homepage"""
        self.client.get("/")
    
    @task(2)
    def view_dashboard(self):
        """View dashboard"""
        self.client.get("/dashboard")
    
    @task(1)
    def create_content(self):
        """Create new content"""
        self.client.post("/api/content", json={
            "title": "Test Content",
            "body": "This is test content",
            "category": "general"
        })
    
    @task(1)
    def view_content_list(self):
        """View content list"""
        self.client.get("/api/content")

2. Lighthouse Testing

// tests/performance/lighthouse.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Performance Tests', () => {
  test('homepage should meet performance benchmarks', async ({ page }) => {
    await page.goto('/');
    
    // Wait for page to fully load
    await page.waitForLoadState('networkidle');
    
    // Check Core Web Vitals
    const performanceMetrics = await page.evaluate(() => {
      return new Promise((resolve) => {
        new PerformanceObserver((list) => {
          const entries = list.getEntries();
          const metrics = {};
          
          entries.forEach((entry) => {
            metrics[entry.name] = entry.value;
          });
          
          resolve(metrics);
        }).observe({ entryTypes: ['navigation', 'paint', 'largest-contentful-paint'] });
      });
    });
    
    // Assert performance metrics
    expect(performanceMetrics['first-contentful-paint']).toBeLessThan(1500);
    expect(performanceMetrics['largest-contentful-paint']).toBeLessThan(2500);
  });

  test('should load images efficiently', async ({ page }) => {
    await page.goto('/');
    
    // Check image loading
    const images = await page.locator('img').all();
    
    for (const img of images) {
      await expect(img).toBeVisible();
      
      // Check if image has loading="lazy" for below-fold images
      const isInViewport = await img.evaluate((el) => {
        const rect = el.getBoundingClientRect();
        return rect.top < window.innerHeight;
      });
      
      if (!isInViewport) {
        await expect(img).toHaveAttribute('loading', 'lazy');
      }
    }
  });
});

API Testing

1. API Contract Testing

// tests/api/auth.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Authentication API', () => {
  test('POST /api/auth/register should register new user', async ({ request }) => {
    const response = await request.post('/api/auth/register', {
      data: {
        email: 'newuser@example.com',
        password: 'SecurePass123!',
        firstName: 'New',
        lastName: 'User'
      }
    });

    expect(response.status()).toBe(201);
    
    const body = await response.json();
    expect(body.success).toBe(true);
    expect(body.data.user.email).toBe('newuser@example.com');
    expect(body.data.user.password).toBeUndefined();
    expect(body.data.token).toBeDefined();
  });

  test('POST /api/auth/login should authenticate user', async ({ request }) => {
    // First register a user
    await request.post('/api/auth/register', {
      data: {
        email: 'logintest@example.com',
        password: 'SecurePass123!',
        firstName: 'Login',
        lastName: 'Test'
      }
    });

    // Then login
    const response = await request.post('/api/auth/login', {
      data: {
        email: 'logintest@example.com',
        password: 'SecurePass123!'
      }
    });

    expect(response.status()).toBe(200);
    
    const body = await response.json();
    expect(body.success).toBe(true);
    expect(body.data.token).toBeDefined();
  });

  test('should handle rate limiting', async ({ request }) => {
    const requests = [];
    
    // Make multiple requests rapidly
    for (let i = 0; i < 15; i++) {
      requests.push(request.post('/api/auth/login', {
        data: {
          email: 'test@example.com',
          password: 'wrongpassword'
        }
      }));
    }

    const responses = await Promise.all(requests);
    
    // Check that some requests are rate limited
    const rateLimited = responses.filter(r => r.status() === 429);
    expect(rateLimited.length).toBeGreaterThan(0);
  });
});

2. API Security Testing

// tests/api/security.spec.ts
import { test, expect } from '@playwright/test';

test.describe('API Security', () => {
  test('should prevent SQL injection', async ({ request }) => {
    const maliciousInputs = [
      "'; DROP TABLE users; --",
      "' OR '1'='1",
      "admin'--",
      "' UNION SELECT * FROM users --"
    ];

    for (const input of maliciousInputs) {
      const response = await request.post('/api/auth/login', {
        data: {
          email: input,
          password: 'password'
        }
      });

      // Should return validation error, not SQL error
      expect(response.status()).toBe(422);
      
      const body = await response.json();
      expect(body.message).toContain('validation');
    }
  });

  test('should prevent XSS attacks', async ({ request }) => {
    const xssPayloads = [
      "<script>alert('xss')</script>",
      "javascript:alert('xss')",
      "<img src=x onerror=alert('xss')>"
    ];

    for (const payload of xssPayloads) {
      const response = await request.post('/api/content', {
        data: {
          title: payload,
          body: 'Test content',
          category: 'general'
        },
        headers: {
          'Authorization': 'Bearer valid-token'
        }
      });

      if (response.status() === 201) {
        const body = await response.json();
        // Content should be sanitized
        expect(body.data.title).not.toContain('<script>');
        expect(body.data.title).not.toContain('javascript:');
      }
    }
  });
});

Production Insights

1. Testing Metrics from forms-service

Test Coverage Results

  • Unit Tests: 85% coverage
  • Integration Tests: 92% API endpoint coverage
  • E2E Tests: 100% critical user flows covered
  • Accessibility Tests: 100% WCAG AA compliance

Performance Benchmarks

  • Test Execution Time:
    • Unit tests: 15 seconds
    • Integration tests: 45 seconds
    • E2E tests: 3 minutes
    • Full test suite: 5 minutes

Bug Detection Efficiency

  • Unit Tests: Caught 70% of bugs before integration
  • Integration Tests: Caught 85% of API issues
  • E2E Tests: Caught 95% of user-facing issues
  • Accessibility Tests: Prevented 100% of accessibility regressions

2. Test Strategy Evolution

Phase 1: Basic Testing

  • Manual testing only
  • Bug detection: 60%
  • Release confidence: Low

Phase 2: Automated Unit Tests

  • Added Jest + React Testing Library
  • Bug detection: 75%
  • Release confidence: Medium

Phase 3: Comprehensive Testing

  • Added E2E, accessibility, and performance tests
  • Bug detection: 95%
  • Release confidence: High

Phase 4: Production Monitoring

  • Added real-time monitoring and alerting
  • Bug detection: 98%
  • Release confidence: Very High

3. Critical Test Cases

Must-Have Tests

  1. User registration and login flows - 100% coverage
  2. Form validation and submission - 100% coverage
  3. API error handling - 100% coverage
  4. Accessibility compliance - 100% coverage
  5. Performance benchmarks - Core Web Vitals

Nice-to-Have Tests

  1. Visual regression testing - UI consistency
  2. Cross-browser compatibility - Multiple browsers
  3. Mobile responsiveness - Different screen sizes
  4. Load testing - High traffic scenarios

4. Testing Best Practices

Test Organization

// Good: Organized test structure
describe('UserService', () => {
  describe('createUser', () => {
    it('should create user with valid data', () => {});
    it('should throw error with invalid email', () => {});
    it('should hash password before saving', () => {});
  });
  
  describe('authenticateUser', () => {
    it('should authenticate with correct credentials', () => {});
    it('should reject invalid credentials', () => {});
    it('should handle rate limiting', () => {});
  });
});

Test Data Management

// Good: Use factories for test data
import { UserFactory } from '../factories/UserFactory';

const testUser = UserFactory.create({
  email: 'test@example.com',
  firstName: 'John'
});

Test Isolation

// Good: Clean state between tests
beforeEach(async () => {
  await testDb.clear();
  await testCache.clear();
});

Continuous Integration

1. GitHub Actions Workflow

# .github/workflows/test.yml
name: Test Suite

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    strategy:
      matrix:
        node-version: [18.x, 20.x]
        
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: ${{ matrix.node-version }}
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Run linting
      run: npm run lint
    
    - name: Run type checking
      run: npm run type-check
    
    - name: Run unit tests
      run: npm run test:coverage
    
    - name: Run E2E tests
      run: npm run e2e
    
    - name: Upload coverage
      uses: codecov/codecov-action@v3
      with:
        file: ./coverage/lcov.info
    
    - name: Upload test results
      uses: actions/upload-artifact@v3
      if: always()
      with:
        name: test-results
        path: test-results/

2. Quality Gates

// Quality gates configuration
const qualityGates = {
  coverage: {
    statements: 80,
    branches: 80,
    functions: 80,
    lines: 80
  },
  performance: {
    firstContentfulPaint: 1500,
    largestContentfulPaint: 2500,
    cumulativeLayoutShift: 0.1
  },
  accessibility: {
    violations: 0,
    wcagLevel: 'AA'
  }
};

Conclusion

This comprehensive testing strategy has been proven in production with the forms-service project, achieving:

  • 95% bug detection before production
  • 100% WCAG AA compliance maintained
  • 5-minute full test suite execution time
  • 98% automated test coverage of critical paths

The combination of unit tests, integration tests, E2E tests, accessibility tests, and performance tests provides a robust safety net for delivering high-quality software with confidence.

Key Takeaways

  1. Start with unit tests - They catch the most bugs with the least effort
  2. Automate everything - Manual testing doesn't scale
  3. Test the critical paths - Focus on what matters most to users
  4. Include accessibility - It's not optional in modern applications
  5. Monitor performance - Speed is a feature, not a bonus
  6. Use real data - Test with data that resembles production
  7. Run tests in CI/CD - Catch issues before they reach production