Testing Procedures Guide

This comprehensive guide covers all testing procedures for the HostingCo system, including unit testing, integration testing, end-to-end testing, and quality assurance processes.

Testing Overview

The HostingCo system employs a multi-layered testing strategy to ensure code quality, functionality, and reliability across all components.

Testing Pyramid

Unit Tests: Individual function and component testing
Integration Tests: API endpoint and database interaction testing
End-to-End Tests: Full application workflow testing
Performance Tests: Load testing and performance benchmarking
Security Tests: Vulnerability scanning and security validation

Automated Testing Scripts

# Run all tests with coverage
./scripts/test.sh all --coverage --report

# Run specific test suites
./scripts/test.sh backend
./scripts/test.sh frontend
./scripts/test.sh shared

# Watch mode for development
./scripts/test-watch.sh all

🔬 Unit Testing

Backend Unit Tests

// Example: UserService.test.ts
import { UserService } from '../services/UserService';
import { User } from '../types/User';

describe('UserService', () => {
  beforeEach(async () => {
    // Setup test database
    await setupTestDatabase();
  });

  afterEach(async () => {
    // Cleanup test database
    await cleanupTestDatabase();
  });

  describe('createUser', () => {
    it('should create a new user with valid data', async () => {
      const userData = {
        email: 'test@example.com',
        name: 'Test User',
        password: 'password123'
      };

      const user = await UserService.createUser(userData);

      expect(user).toBeDefined();
      expect(user.email).toBe(userData.email);
      expect(user.name).toBe(userData.name);
      expect(user.id).toBeDefined();
    });

    it('should throw error for duplicate email', async () => {
      const userData = {
        email: 'existing@example.com',
        name: 'Test User',
        password: 'password123'
      };

      await expect(UserService.createUser(userData))
        .rejects.toThrow('Email already exists');
    });

    it('should hash password before saving', async () => {
      const userData = {
        email: 'test@example.com',
        name: 'Test User',
        password: 'password123'
      };

      const user = await UserService.createUser(userData);

      expect(user.passwordHash).not.toBe(userData.password);
      expect(user.passwordHash).toMatch(/^\$2[aby]\$\d+\$/);
    });
  });
});

Frontend Unit Tests

// Example: UserList.test.tsx
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { UserList } from '../components/UserList';
import { User } from '../types/User';

const mockUsers: User[] = [
  { id: '1', email: 'user1@example.com', name: 'User One' },
  { id: '2', email: 'user2@example.com', name: 'User Two' }
];

describe('UserList', () => {
  it('renders user list correctly', () => {
    render(<UserList users={mockUsers} />);
    
    expect(screen.getByText('User One')).toBeInTheDocument();
    expect(screen.getByText('User Two')).toBeInTheDocument();
    expect(screen.getByText('user1@example.com')).toBeInTheDocument();
    expect(screen.getByText('user2@example.com')).toBeInTheDocument();
  });

  it('calls onUserSelect when user is clicked', () => {
    const mockOnUserSelect = jest.fn();
    render(<UserList users={mockUsers} onUserSelect={mockOnUserSelect} />);
    
    fireEvent.click(screen.getByText('User One'));
    
    expect(mockOnUserSelect).toHaveBeenCalledWith(mockUsers[0]);
  });

  it('shows empty state when no users', () => {
    render(<UserList users={[]} />);
    
    expect(screen.getByText('No users found')).toBeInTheDocument();
  });
});

Test Utilities

// test-utils.ts
import { render } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';

export const renderWithRouter = (component: React.ReactElement) => {
  return render(
    <BrowserRouter>
      {component}
    </BrowserRouter>
  );
};

export const createMockUser = (overrides: Partial<User> = {}): User => ({
  id: 'test-id',
  email: 'test@example.com',
  name: 'Test User',
  ...overrides
});

🔗 Integration Testing

API Integration Tests

// Example: user.integration.test.ts
import request from 'supertest';
import { app } from '../app';
import { setupTestDatabase, cleanupTestDatabase } from '../test-utils';

describe('User API Integration Tests', () => {
  beforeAll(async () => {
    await setupTestDatabase();
  });

  afterAll(async () => {
    await cleanupTestDatabase();
  });

  describe('POST /api/users', () => {
    it('should create a new user', async () => {
      const userData = {
        email: 'test@example.com',
        name: 'Test User',
        password: 'password123'
      };

      const response = await request(app)
        .post('/api/users')
        .send(userData)
        .expect(201);

      expect(response.body).toMatchObject({
        email: userData.email,
        name: userData.name
      });
      expect(response.body.passwordHash).toBeUndefined();
    });

    it('should return 400 for invalid data', async () => {
      const invalidData = {
        email: 'invalid-email',
        name: '',
        password: '123'
      };

      const response = await request(app)
        .post('/api/users')
        .send(invalidData)
        .expect(400);

      expect(response.body.errors).toBeDefined();
    });
  });

  describe('GET /api/users', () => {
    it('should return list of users', async () => {
      const response = await request(app)
        .get('/api/users')
        .expect(200);

      expect(Array.isArray(response.body)).toBe(true);
      expect(response.body.length).toBeGreaterThan(0);
    });
  });
});

Database Integration Tests

// Example: database.integration.test.ts
import { knex } from '../database';
import { UserService } from '../services/UserService';

describe('Database Integration Tests', () => {
  beforeEach(async () => {
    await knex('users').del();
  });

  describe('User Database Operations', () => {
    it('should persist user to database', async () => {
      const userData = {
        email: 'test@example.com',
        name: 'Test User',
        passwordHash: 'hashed-password'
      };

      const [user] = await knex('users').insert(userData).returning('*');
      
      expect(user.id).toBeDefined();
      expect(user.email).toBe(userData.email);
      
      const retrieved = await knex('users').where('id', user.id).first();
      expect(retrieved).toMatchObject(userData);
    });

    it('should handle database constraints', async () => {
      const userData = {
        email: 'test@example.com',
        name: 'Test User',
        passwordHash: 'hashed-password'
      };

      await knex('users').insert(userData);

      await expect(knex('users').insert(userData))
        .rejects.toThrow();
    });
  });
});

🎭 End-to-End Testing

E2E Test Setup

// e2e/setup.ts
import { chromium, Browser, Page } from 'playwright';

export interface TestContext {
  browser: Browser;
  page: Page;
}

export const setupE2ETest = async (): Promise<TestContext> => {
  const browser = await chromium.launch({
    headless: process.env.CI === 'true'
  });
  const page = await browser.newPage();
  
  return { browser, page };
};

export const teardownE2ETest = async (context: TestContext): Promise<void> => {
  await context.browser.close();
};

User Workflow Tests

// e2e/user-workflow.spec.ts
import { test, expect } from '@playwright/test';
import { setupE2ETest, teardownE2ETest } from './setup';

test.describe('User Registration and Login', () => {
  let context: TestContext;

  test.beforeAll(async () => {
    context = await setupE2ETest();
  });

  test.afterAll(async () => {
    await teardownE2ETest(context);
  });

  test('should register new user and login', async () => {
    const { page } = context;

    // Navigate to registration page
    await page.goto('/register');

    // Fill registration form
    await page.fill('[data-testid=email]', 'test@example.com');
    await page.fill('[data-testid=name]', 'Test User');
    await page.fill('[data-testid=password]', 'password123');
    await page.fill('[data-testid=confirmPassword]', 'password123');

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

    // Should redirect to login
    await expect(page).toHaveURL('/login');

    // Fill login form
    await page.fill('[data-testid=email]', 'test@example.com');
    await page.fill('[data-testid=password]', 'password123');

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

    // Should redirect to dashboard
    await expect(page).toHaveURL('/dashboard');
    await expect(page.locator('[data-testid=welcome-message]')).toContainText('Test User');
  });

  test('should show error for invalid login', async () => {
    const { page } = context;

    await page.goto('/login');
    
    await page.fill('[data-testid=email]', 'test@example.com');
    await page.fill('[data-testid=password]', 'wrongpassword');
    
    await page.click('[data-testid=login-button]');
    
    await expect(page.locator('[data-testid=error-message]')).toContainText('Invalid credentials');
  });
});

Admin Workflow Tests

// e2e/admin-workflow.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Admin User Management', () => {
  test('should allow admin to manage users', async ({ page }) => {
    // Login as admin
    await page.goto('/login');
    await page.fill('[data-testid=email]', 'admin@example.com');
    await page.fill('[data-testid=password]', 'admin123');
    await page.click('[data-testid=login-button]');

    // Navigate to admin panel
    await page.click('[data-testid=admin-menu]');
    await page.click('[data-testid=user-management]');

    // Verify user list loads
    await expect(page.locator('[data-testid=user-list]')).toBeVisible();
    
    // Create new user
    await page.click('[data-testid=add-user-button]');
    await page.fill('[data-testid=user-email]', 'newuser@example.com');
    await page.fill('[data-testid=user-name]', 'New User');
    await page.click('[data-testid=save-user-button]');

    // Verify user was created
    await expect(page.locator('text=New User')).toBeVisible();
  });
});

Performance Testing

Load Testing

// performance/load-test.ts
import { check, sleep } from 'k6';
import http from 'k6/http';

export const options = {
  stages: [
    { duration: '2m', target: 100 }, // Ramp up to 100 users
    { duration: '5m', target: 100 }, // Stay at 100 users
    { duration: '2m', target: 0 },   // Ramp down to 0 users
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'], // 95% of requests under 500ms
    http_req_failed: ['rate<0.1'],    // Less than 10% failures
  },
};

export default function () {
  const response = http.get('http://localhost:3003/api/health');
  
  check(response, {
    'status is 200': (r) => r.status === 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
  });
  
  sleep(1);
}

Performance Monitoring

# Run performance tests
npm run test:performance

# Run with specific scenarios
npm run test:performance -- --scenario=api-load

# Generate performance report
npm run test:performance -- --report

Database Performance

// performance/db-performance.test.ts
import { performance } from 'perf_hooks';

describe('Database Performance Tests', () => {
  it('should handle concurrent user creation', async () => {
    const startTime = performance.now();
    
    const promises = Array.from({ length: 100 }, () =>
      UserService.createUser({
        email: `user${Math.random()}@example.com`,
        name: 'Test User',
        password: 'password123'
      })
    );

    await Promise.all(promises);
    
    const endTime = performance.now();
    const duration = endTime - startTime;
    
    expect(duration).toBeLessThan(5000); // Should complete in under 5 seconds
  });
});

Test Configuration

Jest Configuration

// jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/tests'],
  testMatch: [
    '**/__tests__/**/*.ts',
    '**/?(*.)+(spec|test).ts'
  ],
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.d.ts',
    '!src/index.ts'
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  },
  setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
  testTimeout: 30000
};

Playwright Configuration

// playwright.config.ts
import { defineConfig } 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',
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
  ],
  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
});

Test Scripts

// package.json scripts
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "test:unit": "jest tests/unit",
    "test:integration": "jest tests/integration",
    "test:e2e": "playwright test",
    "test:e2e:ui": "playwright test --ui",
    "test:performance": "k6 run tests/performance/load-test.js",
    "test:all": "npm run test:unit && npm run test:integration && npm run test:e2e"
  }
}

Continuous Integration

GitHub Actions

# .github/workflows/test.yml
name: Tests

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

jobs:
  test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: hostingco_test
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

      redis:
        image: redis:7
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 6379:6379

    steps:
    - uses: actions/checkout@v3

    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'
        cache: 'npm'

    - name: Install dependencies
      run: npm ci

    - name: Run unit tests
      run: npm run test:unit

    - name: Run integration tests
      run: npm run test:integration
      env:
        DATABASE_URL: postgresql://postgres:postgres@localhost:5432/hostingco_test
        REDIS_URL: redis://localhost:6379

    - name: Install Playwright
      run: npx playwright install --with-deps

    - name: Build application
      run: npm run build

    - name: Run E2E tests
      run: npm run test:e2e

    - name: Upload coverage reports
      uses: codecov/codecov-action@v3
      with:
        file: ./coverage/lcov.info

Quality Gates

Coverage Threshold: 80% minimum coverage
Performance: 95% of requests < 500ms
Security: No high-severity vulnerabilities
E2E Tests: All critical paths tested

Test Best Practices

Writing Good Tests

AAA Pattern: Arrange, Act, Assert
Descriptive Names: Clear test descriptions
Single Responsibility: One assertion per test
Independent Tests: Tests should not depend on each other

Test Data Management

// test-data/factory.ts
import { faker } from '@faker-js/faker';
import { User } from '../types/User';

export const UserFactory = {
  create: (overrides: Partial<User> = {}): User => ({
    id: faker.datatype.uuid(),
    email: faker.internet.email(),
    name: faker.person.fullName(),
    createdAt: new Date(),
    updatedAt: new Date(),
    ...overrides
  }),

  createMany: (count: number, overrides: Partial<User> = {}): User[] =>
    Array.from({ length: count }, () => UserFactory.create(overrides))
};

Mocking and Stubbing

// Example of mocking external service
import { UserService } from '../services/UserService';
import { EmailService } from '../services/EmailService';

jest.mock('../services/EmailService');

describe('UserService with mocked EmailService', () => {
  it('should send welcome email when creating user', async () => {
    const mockSendEmail = jest.mocked(EmailService.sendWelcomeEmail);
    mockSendEmail.mockResolvedValue();

    await UserService.createUser({
      email: 'test@example.com',
      name: 'Test User',
      password: 'password123'
    });

    expect(mockSendEmail).toHaveBeenCalledWith('test@example.com', 'Test User');
  });
});