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
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
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
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');
});
});