Testing Framework
Comprehensive testing strategies and frameworks for GISE methodology. Learn to create robust test suites that ensure code quality and reliability in AI-assisted development.
Testing Philosophy in GISE
AI-Enhanced Testing
- Test Generation: Use AI to create comprehensive test cases
- Test Data Creation: Generate realistic test data and fixtures
- Test Maintenance: Keep tests current with code changes
- Coverage Analysis: Ensure complete test coverage
Quality Assurance
- Multiple Test Types: Unit, integration, end-to-end testing
- Automated Validation: Continuous testing in CI/CD pipeline
- Performance Testing: Load and stress testing
- Security Testing: Vulnerability and penetration testing
Test Pyramid Strategy
Testing Tools & Framework
Recommended Testing Stack
JavaScript/TypeScript:
- Jest: Test runner and assertions
- Supertest: HTTP testing
- MSW: API mocking
- Playwright: End-to-end testing
Configuration Example:
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:integration": "jest --testPathPattern=integration",
"test:e2e": "playwright test"
},
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}
}
Unit Testing
Test Structure Template
import { describe, beforeEach, afterEach, it, expect, jest } from '@jest/globals';
import { UserService } from '../src/services/UserService';
import { UserRepository } from '../src/repositories/UserRepository';
describe('UserService', () => {
let userService: UserService;
let mockRepository: jest.Mocked<UserRepository>;
beforeEach(() => {
mockRepository = {
create: jest.fn(),
findById: jest.fn(),
update: jest.fn(),
delete: jest.fn()
} as jest.Mocked<UserRepository>;
userService = new UserService(mockRepository);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('createUser', () => {
it('should create user successfully', async () => {
// Arrange
const userData = { email: 'test@example.com', name: 'Test User' };
const expectedUser = { id: '123', ...userData };
mockRepository.create.mockResolvedValue(expectedUser);
// Act
const result = await userService.createUser(userData);
// Assert
expect(result).toEqual(expectedUser);
expect(mockRepository.create).toHaveBeenCalledWith(userData);
});
it('should throw error for invalid email', async () => {
// Arrange
const invalidData = { email: 'invalid-email', name: 'Test' };
// Act & Assert
await expect(userService.createUser(invalidData))
.rejects.toThrow('Invalid email format');
});
});
});
Integration Testing
Database Integration Tests
import { describe, beforeAll, afterAll, beforeEach, it, expect } from '@jest/globals';
import { TestDatabase } from '../helpers/TestDatabase';
import { UserRepository } from '../../src/repositories/UserRepository';
describe('UserRepository Integration', () => {
let testDb: TestDatabase;
let userRepository: UserRepository;
beforeAll(async () => {
testDb = new TestDatabase();
await testDb.connect();
userRepository = new UserRepository(testDb.connection);
});
afterAll(async () => {
await testDb.disconnect();
});
beforeEach(async () => {
await testDb.clearData();
});
it('should create and retrieve user', async () => {
// Arrange
const userData = {
email: 'test@example.com',
name: 'Test User',
password: 'hashedpassword'
};
// Act
const createdUser = await userRepository.create(userData);
const retrievedUser = await userRepository.findById(createdUser.id);
// Assert
expect(retrievedUser).toBeDefined();
expect(retrievedUser!.email).toBe(userData.email);
expect(retrievedUser!.name).toBe(userData.name);
});
});
API Integration Tests
import request from 'supertest';
import { app } from '../../src/app';
import { TestDatabase } from '../helpers/TestDatabase';
describe('User API', () => {
let testDb: TestDatabase;
beforeAll(async () => {
testDb = new TestDatabase();
await testDb.connect();
});
afterAll(async () => {
await testDb.disconnect();
});
beforeEach(async () => {
await testDb.clearData();
});
describe('POST /api/users', () => {
it('should create new user', async () => {
const userData = {
email: 'test@example.com',
name: 'Test User',
password: 'securepassword'
};
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(201);
expect(response.body).toHaveProperty('id');
expect(response.body.email).toBe(userData.email);
expect(response.body).not.toHaveProperty('password');
});
it('should return 400 for invalid email', async () => {
const invalidData = {
email: 'invalid-email',
name: 'Test User',
password: 'password'
};
const response = await request(app)
.post('/api/users')
.send(invalidData)
.expect(400);
expect(response.body).toHaveProperty('error');
});
});
});
End-to-End Testing
Playwright E2E Tests
import { test, expect } from '@playwright/test';
test.describe('User Registration Flow', () => {
test('should register new user successfully', async ({ page }) => {
// Navigate to registration page
await page.goto('/register');
// Fill out registration form
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="name"]', 'Test User');
await page.fill('input[name="password"]', 'securepassword');
await page.fill('input[name="confirmPassword"]', 'securepassword');
// Submit form
await page.click('button[type="submit"]');
// Verify success
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('.welcome-message')).toContainText('Welcome, Test User!');
});
test('should show validation errors for invalid data', async ({ page }) => {
await page.goto('/register');
// Submit empty form
await page.click('button[type="submit"]');
// Verify validation errors
await expect(page.locator('.error-email')).toBeVisible();
await expect(page.locator('.error-name')).toBeVisible();
await expect(page.locator('.error-password')).toBeVisible();
});
});
AI-Assisted Test Generation
Test Case Generation Prompts
Unit Test Generation:
Generate comprehensive Jest unit tests for the following TypeScript class:
[PASTE YOUR CODE]
Requirements:
- Test all public methods
- Include happy path and error scenarios
- Mock all dependencies
- Use arrange-act-assert pattern
- Achieve high code coverage
- Include edge cases and boundary conditions
Please provide:
1. Complete test file with imports
2. Mock setup and teardown
3. Test descriptions that explain what's being tested
4. Both positive and negative test cases
Integration Test Generation:
Create integration tests for the following API endpoints:
[PASTE API ROUTES]
Requirements:
- Use supertest for HTTP testing
- Test database interactions
- Include authentication scenarios
- Test request validation
- Verify response formats
- Cover error handling
Database setup:
- PostgreSQL with test database
- Clean database state between tests
- Use realistic test data
Test Data Management
Fixtures and Factories
// Test fixtures
export const userFixtures = {
validUser: {
email: 'john.doe@example.com',
name: 'John Doe',
password: 'securepassword123'
},
adminUser: {
email: 'admin@example.com',
name: 'Admin User',
password: 'adminpassword123',
role: 'admin'
}
};
// Test factories
export class UserFactory {
static create(overrides: Partial<CreateUserDto> = {}): CreateUserDto {
return {
email: faker.internet.email(),
name: faker.person.fullName(),
password: 'defaultpassword123',
...overrides
};
}
static createMany(count: number, overrides: Partial<CreateUserDto> = {}): CreateUserDto[] {
return Array.from({ length: count }, () => UserFactory.create(overrides));
}
}
Database Test Helpers
import { Pool } from 'pg';
export class TestDatabase {
private pool: Pool;
async connect(): Promise<void> {
this.pool = new Pool({
connectionString: process.env.TEST_DATABASE_URL,
max: 1 // Single connection for tests
});
// Run migrations
await this.runMigrations();
}
async disconnect(): Promise<void> {
await this.pool.end();
}
async clearData(): Promise<void> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
await client.query('TRUNCATE TABLE users CASCADE');
await client.query('TRUNCATE TABLE projects CASCADE');
await client.query('COMMIT');
} catch (error) {
await client.query('ROLLBACK');
throw error;
} finally {
client.release();
}
}
async seedData(data: any): Promise<void> {
// Insert test data
const client = await this.pool.connect();
try {
// Insert users
for (const user of data.users || []) {
await client.query(
'INSERT INTO users (email, name, password) VALUES ($1, $2, $3)',
[user.email, user.name, user.password]
);
}
} finally {
client.release();
}
}
get connection(): Pool {
return this.pool;
}
}
Performance Testing
Load Testing with Artillery
# artillery-config.yml
config:
target: 'http://localhost:3000'
phases:
- duration: 60
arrivalRate: 10
name: "Warm up"
- duration: 120
arrivalRate: 50
name: "Load test"
- duration: 60
arrivalRate: 100
name: "Stress test"
scenarios:
- name: "User registration and login"
weight: 70
flow:
- post:
url: "/api/auth/register"
json:
email: "{{ $randomEmail() }}"
name: "{{ $randomString() }}"
password: "testpassword123"
- post:
url: "/api/auth/login"
json:
email: "{{ email }}"
password: "testpassword123"
- name: "User profile operations"
weight: 30
flow:
- get:
url: "/api/users/profile"
headers:
Authorization: "Bearer {{ $randomString() }}"
Security Testing
Security Test Cases
describe('Security Tests', () => {
describe('Authentication', () => {
it('should reject requests without valid JWT', async () => {
const response = await request(app)
.get('/api/users/profile')
.expect(401);
expect(response.body).toHaveProperty('error', 'Unauthorized');
});
it('should reject expired JWT tokens', async () => {
const expiredToken = generateExpiredToken();
const response = await request(app)
.get('/api/users/profile')
.set('Authorization', `Bearer ${expiredToken}`)
.expect(401);
});
});
describe('Input Validation', () => {
it('should prevent SQL injection', async () => {
const maliciousInput = "'; DROP TABLE users; --";
const response = await request(app)
.post('/api/users')
.send({ email: maliciousInput, name: 'Test', password: 'pass' })
.expect(400);
expect(response.body).toHaveProperty('error');
});
it('should sanitize XSS attempts', async () => {
const xssPayload = '<script>alert("xss")</script>';
const response = await request(app)
.post('/api/users')
.send({ name: xssPayload, email: 'test@example.com', password: 'pass' })
.expect(400);
});
});
});
Test Organization
Directory Structure
tests/
├── unit/ # Unit tests
│ ├── services/
│ ├── controllers/
│ └── utils/
├── integration/ # Integration tests
│ ├── api/
│ ├── database/
│ └── external-services/
├── e2e/ # End-to-end tests
│ ├── user-flows/
│ └── admin-flows/
├── performance/ # Performance tests
│ ├── load/
│ └── stress/
├── security/ # Security tests
├── fixtures/ # Test data
│ ├── users.json
│ └── projects.json
├── helpers/ # Test utilities
│ ├── TestDatabase.ts
│ ├── MockServices.ts
│ └── TestFactories.ts
└── setup/ # Test setup
├── jest.config.js
├── setup.ts
└── teardown.ts
Continuous Integration
GitHub Actions Test Pipeline
name: Test Suite
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm run test:unit -- --coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
integration-tests:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: testpassword
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run database migrations
run: npm run migrate
env:
DATABASE_URL: postgresql://postgres:testpassword@localhost:5432/test
- name: Run integration tests
run: npm run test:integration
env:
DATABASE_URL: postgresql://postgres:testpassword@localhost:5432/test
e2e-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: npx playwright install
- name: Run E2E tests
run: npm run test:e2e
Best Practices
Testing Guidelines
✅ Write tests before or alongside code
✅ Test behavior, not implementation details
✅ Use descriptive test names
✅ Keep tests independent and isolated
✅ Mock external dependencies
✅ Test edge cases and error conditions
✅ Maintain test code quality
✅ Run tests in CI/CD pipeline
Common Anti-Patterns
❌ Testing implementation details
❌ Shared test state between tests
❌ Overly complex test setup
❌ Testing framework code
❌ Ignoring test failures
❌ Poor test coverage
❌ Slow, unreliable tests
Related Topics
Next: AI Testing Tutorials | Code Quality
Comprehensive testing is the foundation of reliable, maintainable software.