Skip to main content

Testing Strategy & Framework

Testing Pyramid

Test Categories

Unit Tests

// Example Unit Test
describe('PaymentService', () => {
let service: PaymentService;
let mockRepository: MockType<PaymentRepository>;

beforeEach(() => {
mockRepository = {
save: jest.fn(),
findById: jest.fn()
};
service = new PaymentService(mockRepository);
});

describe('processPayment', () => {
it('should process valid payment', async () => {
const payment = {
amount: 100,
currency: 'SAR',
userId: '123'
};

mockRepository.save.mockResolvedValue({ ...payment, id: '456' });

const result = await service.processPayment(payment);
expect(result.id).toBe('456');
expect(mockRepository.save).toHaveBeenCalledWith(payment);
});

it('should throw error for invalid amount', async () => {
const payment = {
amount: -100,
currency: 'SAR',
userId: '123'
};

await expect(service.processPayment(payment))
.rejects
.toThrow('Invalid payment amount');
});
});
});

Integration Tests

// Example Integration Test
describe('Payment Integration', () => {
let app: INestApplication;
let dbConnection: Connection;

beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();

app = moduleRef.createNestApplication();
await app.init();

dbConnection = moduleRef.get<Connection>(Connection);
});

afterAll(async () => {
await dbConnection.close();
await app.close();
});

describe('POST /payments', () => {
it('should create payment', async () => {
const response = await request(app.getHttpServer())
.post('/payments')
.send({
amount: 100,
currency: 'SAR',
userId: '123'
});

expect(response.status).toBe(201);
expect(response.body).toHaveProperty('id');

// Verify database state
const payment = await dbConnection
.getRepository(Payment)
.findOne(response.body.id);
expect(payment).toBeDefined();
});
});
});

E2E Tests

// Example E2E Test
describe('Payment Flow', () => {
let page: Page;

beforeAll(async () => {
page = await browser.newPage();
await page.goto('https://staging.oanfinance.com');
});

afterAll(async () => {
await page.close();
});

it('should complete payment process', async () => {
// Login
await page.fill('#email', 'test@example.com');
await page.fill('#password', 'password');
await page.click('#login-button');

// Navigate to payment
await page.click('#make-payment');

// Fill payment details
await page.fill('#amount', '100');
await page.selectOption('#currency', 'SAR');

// Submit payment
await page.click('#submit-payment');

// Verify success
await expect(page.locator('.success-message'))
.toHaveText('Payment Successful');
});
});

Test Coverage Requirements

Coverage Thresholds

// jest.config.js
module.exports = {
coverageThreshold: {
global: {
statements: 80,
branches: 80,
functions: 80,
lines: 80
},
'./src/core/': {
statements: 90,
branches: 90,
functions: 90,
lines: 90
}
}
};

Test Environment Setup

Docker Compose for Testing

version: '3.8'
services:
test-db:
image: postgres:13
environment:
POSTGRES_DB: oan_test
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_pass
ports:
- "5432:5432"

test-redis:
image: redis:6
ports:
- "6379:6379"

test-mocks:
image: wiremock/wiremock
ports:
- "8080:8080"
volumes:
- ./mocks:/home/wiremock

Mocking Strategies

API Mocking

// Mock External API
const mockRiyadBankAPI = {
processPayment: jest.fn().mockResolvedValue({
transactionId: '123',
status: 'success'
}),
getStatus: jest.fn().mockResolvedValue('completed')
};

// Use in Tests
test('payment processing', async () => {
const result = await paymentService.process({
amount: 100,
currency: 'SAR'
});

expect(mockRiyadBankAPI.processPayment).toHaveBeenCalled();
expect(result.status).toBe('success');
});

Test Data Management

Data Factories

// User Factory
export const createUser = Factory.define<User>(() => ({
id: faker.datatype.uuid(),
name: faker.name.fullName(),
email: faker.internet.email(),
createdAt: faker.date.recent()
}));

// Payment Factory
export const createPayment = Factory.define<Payment>(() => ({
id: faker.datatype.uuid(),
amount: faker.datatype.number({ min: 100, max: 1000 }),
currency: 'SAR',
status: faker.helpers.arrayElement(['pending', 'completed', 'failed']),
createdAt: faker.date.recent()
}));

CI Integration

GitHub Actions Configuration

name: Test Pipeline

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

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

- name: Install Dependencies
run: npm ci

- name: Run Linting
run: npm run lint

- name: Run Unit Tests
run: npm run test:unit

- name: Run Integration Tests
run: npm run test:integration

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

- name: Upload Coverage
uses: actions/upload-artifact@v2
with:
name: coverage
path: coverage/

Performance Testing

Load Testing with k6

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
stages: [
{ duration: '1m', target: 50 }, // Ramp up
{ duration: '3m', target: 50 }, // Stay at 50 users
{ duration: '1m', target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95% requests within 500ms
http_req_failed: ['rate<0.01'], // Less than 1% failures
},
};

export default function () {
const response = http.get('https://api.oanfinance.com/health');
check(response, {
'status is 200': (r) => r.status === 200,
});
sleep(1);
}

Security Testing

OWASP ZAP Integration

name: Security Scan

on:
schedule:
- cron: '0 0 * * *'

jobs:
zap_scan:
runs-on: ubuntu-latest

steps:
- name: ZAP Scan
uses: zaproxy/action-full-scan@v0.4.0
with:
target: 'https://staging.oanfinance.com'

Best Practices

Testing Guidelines

  1. Write tests first (TDD)
  2. Keep tests simple and focused
  3. Use meaningful test descriptions
  4. Maintain test independence
  5. Clean up test data

Code Quality

  1. Regular test maintenance
  2. Consistent naming conventions
  3. Proper error handling
  4. Clear test documentation
  5. Efficient test execution

Review Process

  1. Test coverage review
  2. Code quality check
  3. Performance impact
  4. Security implications
  5. Documentation updates