Testing Guide
FlexGate Proxy Testing Documentation
Table of Contents
Overview
FlexGate uses Jest as the testing framework with comprehensive unit and integration tests to ensure code quality and reliability.
Test Philosophy
- High Coverage: Maintain >80% code coverage
- Fast Execution: Tests should run quickly
- Isolation: Tests should not depend on external services
- Clarity: Test names should clearly describe what they test
Test Structure
flexgate-proxy/
├── __tests__/ # Integration tests
│ └── app.test.js # Application-level tests
├── src/
│ ├── config/
│ │ └── __tests__/ # Unit tests for config
│ │ ├── schema.test.js
│ │ └── loader.test.js
│ └── ...
├── tests/
│ └── setup.js # Global test setup
├── jest.config.json # Jest configuration
└── package.json # Test scriptsTest Types
Unit Tests
- Located in
src/**/__tests__/ - Test individual modules in isolation
- Mock external dependencies
- Fast execution
Integration Tests
- Located in
__tests__/ - Test multiple components together
- Test HTTP endpoints
- Verify system behavior
Running Tests
All Tests
npm testRuns all tests with coverage reporting.
Watch Mode
npm run test:watchRuns tests in watch mode - automatically reruns tests when files change.
Unit Tests Only
npm run test:unitRuns only unit tests in src/ directories.
Integration Tests Only
npm run test:integrationRuns only integration tests in __tests__/ directories.
CI Mode
npm run test:ciRuns tests in CI environment with optimizations:
- Parallel execution limited to 2 workers
- No watch mode
- CI-specific reporting
Debug Mode
npm run test:debugRuns tests with Node.js debugger attached. Use with Chrome DevTools.
Test Coverage
Coverage Thresholds
Minimum coverage requirements (enforced):
{
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}Viewing Coverage
After running npm test, open:
coverage/lcov-report/index.htmlCoverage Reports
Coverage reports are generated in multiple formats:
- HTML:
coverage/lcov-report/index.html - LCOV:
coverage/lcov.info(for CI tools) - Text: Displayed in terminal
- JSON:
coverage/coverage-final.json
Writing Tests
Unit Test Example
// src/config/__tests__/schema.test.js
const { validateConfig } = require('../schema');
describe('Config Schema', () => {
test('should validate minimal config', () => {
const config = {
upstreams: [{ name: 'test', url: 'https://api.example.com' }],
routes: [{ path: '/api/*', upstream: 'test' }]
};
const result = validateConfig(config);
expect(result.error).toBeUndefined();
});
});Integration Test Example
// __tests__/app.test.js
const request = require('supertest');
const app = require('../app');
describe('Health Endpoints', () => {
test('GET /health should return 200', async () => {
const response = await request(app)
.get('/health')
.expect(200);
expect(response.body.status).toBe('UP');
});
});Best Practices
Descriptive Test Names
javascript// Good test('should reject config with duplicate upstream names', () => {}); // Bad test('test1', () => {});Arrange-Act-Assert Pattern
javascripttest('example', () => { // Arrange: Set up test data const config = { ... }; // Act: Execute the code const result = validateConfig(config); // Assert: Verify the outcome expect(result.error).toBeUndefined(); });Use beforeEach/afterEach for Setup/Teardown
javascriptdescribe('Feature', () => { let mockData; beforeEach(() => { mockData = createMockData(); }); afterEach(() => { jest.clearAllMocks(); }); test('test case', () => { // Test using mockData }); });Mock External Dependencies
javascriptjest.mock('fs'); const fs = require('fs'); test('should load file', () => { fs.readFileSync.mockReturnValue('mock content'); // Test code });Test Edge Cases
javascriptdescribe('Edge Cases', () => { test('should handle empty input', () => {}); test('should handle null values', () => {}); test('should handle large inputs', () => {}); });
Test Utilities
Global Test Helpers
Available via global.testHelpers:
waitFor
await global.testHelpers.waitFor(() => condition === true, 5000);mockRequest
const req = global.testHelpers.mockRequest({
method: 'POST',
path: '/api/users',
body: { name: 'John' }
});mockResponse
const res = global.testHelpers.mockResponse();
res.status(200).json({ success: true });
expect(res.statusCode).toBe(200);Mocking
Mocking Modules
// Mock entire module
jest.mock('redis');
// Mock specific functions
jest.mock('fs', () => ({
readFileSync: jest.fn(),
writeFileSync: jest.fn()
}));Mocking with Return Values
const fs = require('fs');
fs.readFileSync.mockReturnValue('content');
fs.readFileSync.mockReturnValueOnce('first call').mockReturnValue('subsequent calls');Spy on Methods
const spy = jest.spyOn(console, 'log');
// Execute code
expect(spy).toHaveBeenCalledWith('expected message');
spy.mockRestore();CI/CD Integration
GitHub Actions Example
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
- run: npm run test:ci
- uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.infoPre-commit Hook
Add to .git/hooks/pre-commit:
#!/bin/sh
npm run lint && npm run test:ciOr use Husky:
npm install --save-dev husky
npx husky install
npx husky add .husky/pre-commit "npm run validate"Debugging Tests
Run Single Test File
npx jest src/config/__tests__/schema.test.jsRun Specific Test
npx jest -t "should validate minimal config"Debug with Chrome DevTools
- Run:
npm run test:debug - Open Chrome:
chrome://inspect - Click "inspect" on the Node process
- Set breakpoints and debug
Verbose Output
npx jest --verboseCommon Test Patterns
Testing Async Code
test('async operation', async () => {
const result = await asyncFunction();
expect(result).toBe(expected);
});Testing Promises
test('promise rejection', async () => {
await expect(failingFunction()).rejects.toThrow('error message');
});Testing Callbacks
test('callback function', (done) => {
callbackFunction((err, result) => {
expect(err).toBeNull();
expect(result).toBe(expected);
done();
});
});Testing Timers
jest.useFakeTimers();
test('timer test', () => {
const callback = jest.fn();
setTimeout(callback, 1000);
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalled();
});Test Matchers
Common Matchers
// Equality
expect(value).toBe(expected);
expect(value).toEqual(expected);
// Truthiness
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();
// Numbers
expect(value).toBeGreaterThan(3);
expect(value).toBeLessThan(5);
expect(value).toBeCloseTo(0.3);
// Strings
expect(string).toMatch(/pattern/);
expect(string).toContain('substring');
// Arrays
expect(array).toContain(item);
expect(array).toHaveLength(3);
// Objects
expect(obj).toHaveProperty('key');
expect(obj).toMatchObject({ key: 'value' });
// Exceptions
expect(() => fn()).toThrow();
expect(() => fn()).toThrow('error message');
// Functions
expect(fn).toHaveBeenCalled();
expect(fn).toHaveBeenCalledWith(arg1, arg2);
expect(fn).toHaveBeenCalledTimes(2);Troubleshooting
Tests Hanging
- Check for missing
done()in callback tests - Ensure all promises are resolved/rejected
- Look for open connections (Redis, DB)
Flaky Tests
- Avoid testing timing-sensitive code without mocking timers
- Mock external services
- Don't depend on execution order
Low Coverage
- Run with
--coverageto see uncovered lines - Focus on critical paths first
- Add edge case tests
Current Test Coverage
As of Phase 0 completion:
Config Schema Tests
- ✅ 100+ test cases
- ✅ Valid/invalid config validation
- ✅ Upstream validation
- ✅ Route validation
- ✅ Circuit breaker config
- ✅ Rate limit config
- ✅ Migration logic
- ✅ Edge cases
Config Loader Tests
- ✅ 30+ test cases
- ✅ YAML loading
- ✅ Validation integration
- ✅ Config reloading
- ✅ Watchers
- ✅ Error handling
Application Tests
- ✅ 25+ test cases
- ✅ Health endpoints
- ✅ Version endpoint
- ✅ Metrics endpoint
- ✅ Middleware
- ✅ Error handling
- ✅ CORS support
Total: 150+ test cases
Next Steps
- Add tests for circuit breaker module
- Add tests for rate limiter module
- Add tests for logger module
- Set up continuous testing in CI/CD
- Add performance benchmarks
- Add load testing
Last Updated: January 27, 2026
Testing Framework: Jest 29.7.0
Coverage Threshold: 80%