@oxlayer/capabilities-testing
v0.1.4
Published
Shared testing infrastructure for OxLayer apps
Readme
@oxlayer/capabilities-testing
Shared testing infrastructure for OxLayer applications.
Overview
This package provides reusable testing utilities for OxLayer microservices, including:
- MockTracer - OpenTelemetry tracer mock for testing without OTEL setup
- MockEventBus - In-memory event bus for testing event-driven functionality
- MockDomainEventEmitter - Mock for ClickHouse domain events
- MockBusinessMetricEmitter - Mock for ClickHouse business metrics
- Test Helpers - Common utilities (waitFor, retry, delay, etc.)
- Assertions - Custom assertion helpers for common test scenarios
Installation
pnpm add -D @oxlayer/capabilities-testingUsage
MockTracer
Mock OpenTelemetry tracer for testing without actual OTEL setup:
import { MockTracer } from '@oxlayer/capabilities-testing';
const tracer = new MockTracer();
await tracer.startActiveSpan('database.query', { kind: 'CLIENT' }, async (span) => {
span?.setAttribute('db.system', 'postgresql');
span?.setAttribute('db.statement', 'SELECT * FROM todos');
await queryDatabase();
span?.setStatus({ code: 1 });
});MockEventBus
In-memory event bus for testing event-driven functionality:
import { MockEventBus } from '@oxlayer/capabilities-testing';
const eventBus = new MockEventBus();
// Emit events
await eventBus.emit(new TodoCreatedEvent({
aggregateId: 'todo-1',
userId: 'user-1',
title: 'Test Todo'
}));
// Assert events were published
expect(eventBus.wasPublished('Todo.Created')).toBe(true);
expect(eventBus.count('Todo.Created')).toBe(1);
// Get events for detailed assertions
const events = eventBus.getEvents('Todo.Created');
expect(events[0].event.payload.title).toBe('Test Todo');
// Clear for next test
eventBus.clear();MockDomainEventEmitter
Mock for ClickHouse domain events:
import { MockDomainEventEmitter } from '@oxlayer/capabilities-testing';
const eventEmitter = new MockDomainEventEmitter();
// Emit domain events
await eventEmitter.emit('Todo.Created', {
aggregateId: 'todo-1',
userId: 'user-1',
title: 'Test Todo'
});
// Assert
expect(eventEmitter.wasEventEmitted('Todo.Created')).toBe(true);
expect(eventEmitter.count('Todo.Created')).toBe(1);
// Clear for next test
eventEmitter.clear();MockBusinessMetricEmitter
Mock for ClickHouse business metrics:
import { MockBusinessMetricEmitter } from '@oxlayer/capabilities-testing';
const metricEmitter = new MockBusinessMetricEmitter();
// Record metrics
await metricEmitter.increment('todos.created', { userId: 'user-1' });
await metricEmitter.gauge('todos.active_count', 5);
await metricEmitter.record('todos.duration', 150, 'histogram');
// Assert
expect(metricEmitter.wasMetricRecorded('todos.created')).toBe(true);
expect(metricEmitter.getCounterValue('todos.created')).toBe(1);
expect(metricEmitter.getGaugeValue('todos.active_count')).toBe(5);
// Clear for next test
metricEmitter.clear();Test Helpers
Common utilities for testing:
import { waitFor, retry, delay, measureTime } from '@oxlayer/capabilities-testing';
// Wait for a condition
await waitFor(() => eventBus.wasPublished('Todo.Created'));
// Retry with backoff
const result = await retry(
() => fetch(url),
{ maxAttempts: 3, initialDelay: 100 }
);
// Delay
await delay(100);
// Measure execution time
const [data, duration] = await measureTime(() => expensiveOperation());
console.log(`Operation took ${duration}ms`);Assertions
Custom assertion helpers:
import {
assertEventPublished,
assertMetricRecorded,
assertThrows,
assertRejects
} from '@oxlayer/capabilities-testing';
// Assert an event was published
assertEventPublished(eventBus, 'Todo.Created');
assertEventPublished(eventBus, 'Todo.Created', 2); // Exactly 2 times
// Assert a metric was recorded
assertMetricRecorded(metricEmitter, 'todos.created');
// Assert a function throws
assertThrows(() => riskyOperation(), Error, 'Expected error message');
// Assert a promise rejects
await assertRejects(async () => {
throw new ValidationError('Invalid');
}, ValidationError);Test Setup Example
Recommended test setup pattern:
import { beforeEach, afterEach, describe, it, expect } from 'bun:test';
import { MockEventBus, MockDomainEventEmitter, MockBusinessMetricEmitter, MockTracer } from '@oxlayer/capabilities-testing';
describe('Todo Use Cases', () => {
let eventBus: MockEventBus;
let eventEmitter: MockDomainEventEmitter;
let metricEmitter: MockBusinessMetricEmitter;
let tracer: MockTracer;
beforeEach(() => {
eventBus = new MockEventBus();
eventEmitter = new MockDomainEventEmitter();
metricEmitter = new MockBusinessMetricEmitter();
tracer = new MockTracer();
});
afterEach(() => {
eventBus.clear();
eventEmitter.clear();
metricEmitter.clear();
tracer.reset();
});
it('should create a todo and emit events', async () => {
const useCase = new CreateTodoUseCase(
repository,
eventBus,
eventEmitter,
metricEmitter,
tracer
);
const result = await useCase.execute({
title: 'Test Todo',
userId: 'user-1'
});
expect(result.success).toBe(true);
assertEventPublished(eventBus, 'Todo.Created');
assertDomainEventEmitted(eventEmitter, 'Todo.Created');
assertMetricRecorded(metricEmitter, 'todos.created');
});
});API Reference
MockTracer
startActiveSpan(name, options, fn)- Execute function within span contextstartSpan(name, options)- Create a manual spangetTracer()- Get tracer instance (returns self)getSpanCount()- Get number of spans createdreset()- Reset span counter
MockEventBus
emit(event)- Emit a domain eventemitEnvelope(envelope)- Emit an event envelopeon(eventType, handler)- Subscribe to eventsonEnvelope(eventType, handler)- Subscribe to event envelopesgetEvents(eventType)- Get events by typegetAllEvents()- Get all eventsclear()- Clear all eventswasPublished(eventType)- Check if event was publishedcount(eventType)- Count events by type
MockDomainEventEmitter
emit(eventName, payload, metadata)- Emit a domain eventgetEvents()- Get all eventsgetEventsByName(eventName)- Get events by namegetLastEvent()- Get most recent eventclear()- Clear all eventswasEventEmitted(eventName)- Check if event was emittedcount(eventName)- Count events by name
MockBusinessMetricEmitter
increment(metricName, dimensions)- Increment a countergauge(metricName, value, dimensions)- Record a gaugerecord(metricName, value, kind, dimensions)- Record a metricgetMetrics()- Get all metricsgetCounterValue(metricName)- Get counter valuegetGaugeValue(metricName)- Get last gauge valueclear()- Clear all metricswasMetricRecorded(metricName)- Check if metric was recordedcount(metricName)- Count metrics by name
Test Helpers
waitFor(condition, options)- Wait for condition to be trueretry(fn, options)- Retry with exponential backoffdelay(ms)- Delay for specified timetimeout(ms, value)- Promise that resolves after timeouttimeoutError(ms, message)- Promise that rejects after timeoutmeasureTime(fn)- Measure execution timeconcurrent(fns)- Run functions concurrentlycreateSpy(fn)- Create a spy functionstubMethod(obj, method, fn)- Stub a method
Assertions
assertEventPublished(eventBus, eventType, count?)- Assert event was publishedassertEventNotPublished(eventBus, eventType)- Assert event was NOT publishedassertDomainEventEmitted(emitter, eventName, count?)- Assert domain event was emittedassertMetricRecorded(emitter, metricName, count?)- Assert metric was recordedassertCounterValue(emitter, metricName, value)- Assert counter valueassertGaugeValue(emitter, metricName, value)- Assert gauge valueassertDeepEqual(actual, expected)- Assert deep equalityassertInstanceOf(value, constructor)- Assert instance typeassertThrows(fn, errorType?, message?)- Assert function throwsassertRejects(fn, errorType?, message?)- Assert promise rejects
License
MIT
