DEV Community

Cover image for Building Confidence at Scale: A Deep Dive into Cipher Horizon's Testing Architecture
Daniele Minatto
Daniele Minatto

Posted on

Building Confidence at Scale: A Deep Dive into Cipher Horizon's Testing Architecture

Testing a microservices architecture presents unique challenges due to distributed components, complex interactions, and various failure modes. This comprehensive guide details how Cipher Horizon implements testing strategies to ensure system reliability and quality.

Understanding Testing Requirements

Before implementing our testing strategy, we identified key requirements:

  1. Coverage Requirements
    • Unit test coverage > 80%
    • Integration test coverage > 70%
    • Critical path E2E coverage 100%
  2. Performance Criteria
    • Response time < 200ms (95th percentile)
    • Throughput > 1000 RPS
    • Error rate < 0.1%
  3. Quality Gates
    • Code quality metrics
    • Security scanning
    • Performance benchmarks

Unit Testing Implementation

Core Testing Framework

Our unit testing strategy focuses on isolation and comprehensive coverage:

// Service Test Implementation with Detailed Mocking
describe('UserService', () => {
    let service: UserService;
    let repository: MockType<Repository<User>>;
    let eventEmitter: MockType<EventEmitter>;
    let cacheManager: MockType<CacheManager>;

    beforeEach(async () => {
        // Create testing module with comprehensive mocking
        const module = await Test.createTestingModule({
            providers: [
                UserService,
                {
                    provide: getRepositoryToken(User),
                    useFactory: repositoryMockFactory
                },
                {
                    provide: EventEmitter,
                    useFactory: eventEmitterMockFactory
                },
                {
                    provide: CacheManager,
                    useFactory: cacheManagerMockFactory
                }
            ]
        }).compile();

        // Get service and mocked dependencies
        service = module.get<UserService>(UserService);
        repository = module.get(getRepositoryToken(User));
        eventEmitter = module.get(EventEmitter);
        cacheManager = module.get(CacheManager);
    });

    describe('createUser', () => {
        it('should create user and emit event', async () => {
            // Arrange
            const userData = {
                email: 'test@example.com',
                name: 'Test User'
            };
            const savedUser = { ...userData, id: '1' };
            repository.save.mockResolvedValue(savedUser);

            // Act
            const result = await service.createUser(userData);

            // Assert
            expect(result).toEqual(savedUser);
            expect(repository.save).toHaveBeenCalledWith(userData);
            expect(eventEmitter.emit).toHaveBeenCalledWith(
                'user.created',
                savedUser
            );
            expect(cacheManager.set).toHaveBeenCalledWith(
                `user:${savedUser.id}`,
                savedUser
            );
        });

        // Test error scenarios
        it('should handle database errors gracefully', async () => {
            // Arrange
            repository.save.mockRejectedValue(new DatabaseError());

            // Act & Assert
            await expect(
                service.createUser({ email: 'test@example.com' })
            ).rejects.toThrow(UserCreationError);
            expect(eventEmitter.emit).not.toHaveBeenCalled();
            expect(cacheManager.set).not.toHaveBeenCalled();
        });
    });
});
Enter fullscreen mode Exit fullscreen mode

Testing Utilities

We developed robust testing utilities to support our testing strategy:

// Advanced Mock Factory
class MockFactory<T> {
    create(partial: Partial<T> = {}): jest.Mocked<T> {
        const mock = {
            ...this.getDefaultMock(),
            ...partial
        };

        return this.addSpies(mock);
    }

    private getDefaultMock(): Partial<T> {
        // Implementation specific to each type
        return {};
    }

    private addSpies(mock: T): jest.Mocked<T> {
        const spiedMock = { ...mock };

        Object.keys(mock).forEach(key => {
            if (typeof mock[key] === 'function') {
                spiedMock[key] = jest.fn();
            }
        });

        return spiedMock as jest.Mocked<T>;
    }
}

// Test Data Factory
class TestDataFactory {
    static createUser(override: Partial<User> = {}): User {
        return {
            id: uuid(),
            email: `test-${Date.now()}@example.com`,
            name: 'Test User',
            createdAt: new Date(),
            ...override
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

Integration Testing Framework

Our integration testing strategy ensures proper service interaction:

@Injectable()
class IntegrationTestManager {
    constructor(
        private readonly dbConnection: Connection,
        private readonly redisClient: Redis,
        private readonly kafkaClient: KafkaClient,
        private readonly metrics: TestMetrics
    ) {}

    async setupTestEnvironment(
        config: TestEnvironmentConfig
    ): Promise<TestEnvironment> {
        // Create isolated test environment
        const environment = await this.createIsolatedEnvironment(config);

        try {
            // Initialize test dependencies
            await this.initializeDependencies(environment);

            // Seed test data
            await this.seedTestData(environment);

            // Setup monitoring
            await this.setupMonitoring(environment);

            return environment;
        } catch (error) {
            // Cleanup on failure
            await this.teardown(environment);
            throw error;
        }
    }

    async runIntegrationTest(
        test: (env: TestEnvironment) => Promise<void>
    ): Promise<TestResult> {
        const startTime = Date.now();
        const environment = await this.setupTestEnvironment({
            isolationLevel: 'high',
            cleanup: true
        });

        try {
            await test(environment);
            return this.createSuccessResult(Date.now() - startTime);
        } catch (error) {
            return this.createFailureResult(error, Date.now() - startTime);
        } finally {
            await this.teardown(environment);
        }
    }
}

// Integration Test Example
describe('Order Processing Flow', () => {
    let testManager: IntegrationTestManager;
    let testEnvironment: TestEnvironment;

    beforeAll(async () => {
        testManager = await TestContainer.get(IntegrationTestManager);
    });

    beforeEach(async () => {
        testEnvironment = await testManager.setupTestEnvironment({
            services: ['order', 'payment', 'inventory'],
            databases: ['postgres', 'redis'],
            messageQueues: ['kafka']
        });
    });

    it('should process order end-to-end', async () => {
        await testManager.runIntegrationTest(async (env) => {
            // Create order
            const order = await env.orderService.createOrder({
                items: [{ productId: '1', quantity: 2 }],
                userId: 'test-user'
            });

            // Verify inventory check
            const inventoryCheck = await env.inventoryService
                .getInventoryCheck(order.id);
            expect(inventoryCheck.status).toBe('RESERVED');

            // Process payment
            const payment = await env.paymentService.processPayment({
                orderId: order.id,
                amount: order.totalAmount
            });
            expect(payment.status).toBe('COMPLETED');

            // Verify final order status
            const updatedOrder = await env.orderService
                .getOrder(order.id);
            expect(updatedOrder.status).toBe('PAID');
            expect(updatedOrder.paymentId).toBe(payment.id);

            // Verify events
            const events = await env.eventStore.getEvents(order.id);
            expect(events).toMatchSnapshot();
        });
    });
});
Enter fullscreen mode Exit fullscreen mode

E2E Testing Implementation

Our end-to-end testing strategy focuses on real-world scenarios and user journeys:

E2E Test Framework

@Injectable()
class E2ETestSuite {
    constructor(
        private readonly config: E2EConfig,
        private readonly metrics: TestMetrics,
        private readonly logger: TestLogger
    ) {}

    async runE2EScenario(
        scenario: E2EScenario
    ): Promise<E2ETestResult> {
        const context = await this.createTestContext(scenario);
        const monitor = new PerformanceMonitor(context);

        try {
            // Start monitoring
            monitor.start();

            // Execute scenario steps
            for (const step of scenario.steps) {
                await this.executeStep(step, context);
                await this.validateStepResults(step, context);
            }

            // Collect results
            const results = await monitor.collectResults();
            return this.createSuccessResult(results);
        } catch (error) {
            return this.handleTestFailure(error, monitor);
        } finally {
            await this.cleanup(context);
        }
    }

    private async executeStep(
        step: E2EStep,
        context: TestContext
    ): Promise<void> {
        this.logger.info(`Executing step: ${step.name}`);

        const startTime = Date.now();
        try {
            await step.execute(context);

            this.metrics.recordStepExecution({
                step: step.name,
                duration: Date.now() - startTime,
                status: 'success'
            });
        } catch (error) {
            this.metrics.recordStepExecution({
                step: step.name,
                duration: Date.now() - startTime,
                status: 'failure',
                error
            });
            throw error;
        }
    }
}

// E2E Test Scenario Example
describe('User Registration and Order Flow', () => {
    let e2eSuite: E2ETestSuite;

    beforeAll(async () => {
        e2eSuite = new E2ETestSuite({
            baseUrl: process.env.API_URL,
            timeout: 30000,
            retries: 2
        });
    });

    it('should complete full user journey', async () => {
        const result = await e2eSuite.runE2EScenario({
            name: 'New User Purchase Flow',
            steps: [
                {
                    name: 'Register User',
                    execute: async (context) => {
                        const response = await axios.post(
                            '/api/users',
                            {
                                email: 'test@example.com',
                                password: 'password123'
                            }
                        );
                        context.userId = response.data.id;
                    },
                    validate: async (context) => {
                        const user = await getUserById(context.userId);
                        expect(user.status).toBe('ACTIVE');
                    }
                },
                {
                    name: 'Add Product to Cart',
                    execute: async (context) => {
                        await axios.post(
                            '/api/cart',
                            {
                                userId: context.userId,
                                productId: 'test-product',
                                quantity: 1
                            }
                        );
                    }
                },
                {
                    name: 'Complete Purchase',
                    execute: async (context) => {
                        const response = await axios.post(
                            '/api/orders',
                            {
                                userId: context.userId,
                                paymentMethod: 'credit_card',
                                billingDetails: {
                                    // billing details
                                }
                            }
                        );
                        context.orderId = response.data.id;
                    },
                    validate: async (context) => {
                        const order = await getOrderById(context.orderId);
                        expect(order.status).toBe('COMPLETED');
                    }
                }
            ]
        });

        expect(result.success).toBeTruthy();
        expect(result.metrics.totalDuration).toBeLessThan(5000);
    });
});
Enter fullscreen mode Exit fullscreen mode

Performance Testing Implementation

Load Testing Framework

@Injectable()
class PerformanceTestRunner {
    constructor(
        private readonly metrics: MetricsCollector,
        private readonly config: TestConfig,
        private readonly monitor: SystemMonitor
    ) {}

    async runLoadTest(
        scenario: LoadTestScenario
    ): Promise<LoadTestResults> {
        const results = new LoadTestResults();
        const systemMetrics = await this.monitor.startMonitoring();

        try {
            await this.executeWithConcurrency(
                scenario.concurrentUsers,
                async (userId) => {
                    const userMetrics = await this.executeUserScenario(
                        scenario,
                        userId
                    );
                    results.addUserMetrics(userMetrics);
                }
            );

            const systemResults = await systemMetrics.collect();
            return this.analyzeResults(results, systemResults);
        } finally {
            await systemMetrics.stop();
        }
    }

    private async executeUserScenario(
        scenario: LoadTestScenario,
        userId: string
    ): Promise<UserMetrics> {
        const metrics = new UserMetrics(userId);

        for (const action of scenario.actions) {
            const startTime = Date.now();

            try {
                await action.execute();
                metrics.recordSuccess(
                    action.name,
                    Date.now() - startTime
                );
            } catch (error) {
                metrics.recordError(
                    action.name,
                    error,
                    Date.now() - startTime
                );
            }
        }

        return metrics;
    }
}

// Load Test Scenario Example
describe('API Performance', () => {
    let performanceRunner: PerformanceTestRunner;

    beforeAll(async () => {
        performanceRunner = new PerformanceTestRunner({
            duration: '5m',
            rampUp: '30s',
            coolDown: '30s'
        });
    });

    it('should handle high concurrent users', async () => {
        const results = await performanceRunner.runLoadTest({
            name: 'High Concurrency Test',
            concurrentUsers: 1000,
            actions: [
                {
                    name: 'Get Products',
                    weight: 0.7,
                    execute: async () => {
                        return axios.get('/api/products');
                    }
                },
                {
                    name: 'Create Order',
                    weight: 0.3,
                    execute: async () => {
                        return axios.post('/api/orders', {
                            // order details
                        });
                    }
                }
            ],
            assertions: [
                {
                    metric: 'responseTime',
                    percentile: 95,
                    threshold: 200 // ms
                },
                {
                    metric: 'errorRate',
                    max: 0.01 // 1%
                }
            ]
        });

        expect(results.assertions).toAllPass();
    });
});

Enter fullscreen mode Exit fullscreen mode

Best Practices and Lessons Learned

Test Organization

interface TestingBestPractices {
    organization: {
        unitTests: {
            location: 'alongside source files';
            naming: '*.spec.ts';
            grouping: 'by feature';
        };
        integrationTests: {
            location: 'tests/integration';
            naming: '*.integration.spec.ts';
            isolation: 'per test suite';
        };
        e2eTests: {
            location: 'tests/e2e';
            naming: '*.e2e.spec.ts';
            environment: 'isolated';
        };
    };
    practices: {
        mocking: 'minimal, strategic';
        dataManagement: 'isolated, repeatable';
        assertions: 'meaningful, specific';
    };
}
Enter fullscreen mode Exit fullscreen mode

Test Data Management

@Injectable()
class TestDataManager {
    async setupTestData(
        config: TestDataConfig
    ): Promise<TestData> {
        const transaction = await this.db.startTransaction();

        try {
            const data = await this.generateTestData(config);
            await this.validateTestData(data);
            await transaction.commit();
            return data;
        } catch (error) {
            await transaction.rollback();
            throw error;
        }
    }

    private async generateTestData(
        config: TestDataConfig
    ): Promise<TestData> {
        // Implementation for generating test data
        // based on configuration
    }
}
Enter fullscreen mode Exit fullscreen mode

Continuous Testing Pipeline

# CI/CD Pipeline for Testing
name: Continuous Testing

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]

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 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 Test Results
        uses: actions/upload-artifact@v2
        with:
          name: test-results
          path: coverage/
Enter fullscreen mode Exit fullscreen mode

Looking Ahead: Reflecting on the Journey

As we conclude our testing implementation, our next post will focus on reflecting on the entire Cipher Horizon journey, including:

  1. Architecture Evolution
    • Initial design decisions
    • Architecture adaptations
    • Scaling challenges
    • Future improvements
  2. Team Learning
    • Development practices
    • Collaboration strategies
    • Knowledge sharing
    • Skills development
  3. Technical Insights
    • Technology choices
    • Performance optimizations
    • Security considerations
    • Maintenance strategies

What testing strategies have proven most effective in your microservices architecture? Share your experiences in the comments below!

ACI image

ACI.dev: The Only MCP Server Your AI Agents Need

ACI.dev’s open-source tool-use platform and Unified MCP Server turns 600+ functions into two simple MCP tools on one server—search and execute. Comes with multi-tenant auth and natural-language permission scopes. 100% open-source under Apache 2.0.

Star our GitHub!

Top comments (0)

DevCycle image

Ship Faster, Stay Flexible.

DevCycle is the first feature flag platform with OpenFeature built-in to every open source SDK, designed to help developers ship faster while avoiding vendor-lock in.

Start shipping

Full-Stack Performance & Debugging Workshop

Join the Sentry team for a hands-on workshop where you'll learn to use tracing to understand exactly what your code is doing across the entire app, frontend to backend.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️