Skip to main content

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

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


Next: AI Testing Tutorials | Code Quality

Comprehensive testing is the foundation of reliable, maintainable software.