Testing Serializers
This example demonstrates how to test custom serialization logic using MockSerializerRegistry
and framework-agnostic testing patterns.
📦 Version: This example corresponds to SyntropyLog v0.7.0
Overview
Serializers are functions that transform complex objects into strings for logging. This example shows how to:
- Test custom serializer functions in isolation
- Use
MockSerializerRegistry
for testing - Achieve high test coverage with framework-agnostic patterns
- Test framework boilerplate functions
Key Concepts
MockSerializerRegistry
A framework-agnostic mock that simulates the SerializerRegistry
behavior:
import { MockSerializerRegistry } from 'syntropylog/testing';
// Inject the testing framework's spy function
const mockRegistry = new MockSerializerRegistry(vi.fn);
// Configure mock behavior
mockRegistry.setSerializer('user', (user) => JSON.stringify(user));
mockRegistry.setError('user', new Error('Serialization failed'));
mockRegistry.setTimeout(100); // Simulate timeout
Framework Agnostic Testing
The mock works with any testing framework:
// Vitest
const mockRegistry = new MockSerializerRegistry(vi.fn);
// Jest
const mockRegistry = new MockSerializerRegistry(jest.fn);
// Jasmine
const mockRegistry = new MockSerializerRegistry(jasmine.createSpy);
Spy Function Injection
If you don't inject a spy function, you get a memorable error:
// ❌ This will throw: "SPY FUNCTION NOT INJECTED!"
const mockRegistry = new MockSerializerRegistry();
// ✅ This works perfectly
const mockRegistry = new MockSerializerRegistry(vi.fn);
Example Code
Custom Serializers
// src/serializers.ts
export function serializeUser(user: any): string {
return JSON.stringify({
id: user.id,
name: user.name,
email: user.email,
createdAt: user.createdAt?.toISOString()
});
}
export function serializeOrder(order: any): string {
return JSON.stringify({
id: order.id,
userId: order.userId,
total: order.total,
status: order.status,
items: order.items?.length || 0
});
}
Testing Serializers
// tests/serializer-service.test.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { MockSerializerRegistry } from 'syntropylog/testing';
import { serializeUser, serializeOrder } from '../src/serializers';
describe('Serializer Service', () => {
let mockRegistry: MockSerializerRegistry;
beforeEach(() => {
mockRegistry = new MockSerializerRegistry(vi.fn);
});
it('should serialize user correctly', () => {
const user = {
id: 123,
name: 'John Doe',
email: 'john@example.com',
createdAt: new Date('2023-01-01')
};
mockRegistry.setSerializer('user', serializeUser);
const result = mockRegistry.process('user', user);
expect(result).toBe(JSON.stringify({
id: 123,
name: 'John Doe',
email: 'john@example.com',
createdAt: '2023-01-01T00:00:00.000Z'
}));
});
it('should handle multiple serializers', () => {
const user = { id: 1, name: 'John' };
const order = { id: 100, userId: 1, total: 99.99, status: 'pending' };
mockRegistry.setSerializer('user', serializeUser);
mockRegistry.setSerializer('order', serializeOrder);
const userResult = mockRegistry.process('user', user);
const orderResult = mockRegistry.process('order', order);
expect(userResult).toContain('"name":"John"');
expect(orderResult).toContain('"total":99.99');
});
it('should handle null and undefined values', () => {
mockRegistry.setSerializer('user', serializeUser);
const result1 = mockRegistry.process('user', null);
const result2 = mockRegistry.process('user', undefined);
expect(result1).toBe('null');
expect(result2).toBe('undefined');
});
});
Framework Boilerplate Testing
// tests/example-coverage.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { createTestHelper } from 'syntropylog/testing';
import { initializeSyntropyLog, gracefulShutdown } from '../src/index';
describe('Framework Boilerplate', () => {
const testHelper = createTestHelper(vi.fn);
beforeEach(() => {
testHelper.beforeEach();
});
afterEach(() => {
testHelper.afterEach();
});
it('should initialize SyntropyLog correctly', async () => {
const result = await initializeSyntropyLog();
expect(result).toBeDefined();
});
it('should handle graceful shutdown', async () => {
const result = await gracefulShutdown();
expect(result).toBeDefined();
});
});
Test Coverage
This example achieves 100% test coverage:
Tests: 8 passed, 8 total
Snapshots: 0 total
Time: 1.5s
Ran all test suites.
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Key Takeaways
1. Framework Agnostic Mocks
All mocks work with any testing framework through spy function injection.
2. High Test Coverage
Achieve 100% coverage by testing both business logic and framework boilerplate.
3. Declarative Testing
Tests focus on behavior and outcomes, not implementation details.
4. Zero External Dependencies
No Redis, HTTP servers, or external services needed for testing.
5. Silent Observer Philosophy
Framework errors are reported but never interrupt the application flow.
Related Examples
- Example 28: Vitest Testing - Basic testing patterns
- Example 29: Jest Testing - Jest-specific patterns
- Example 30: Redis Context - Redis testing patterns
- Example 32: Transport Concepts - Transport testing concepts
Next Steps
- Run the example:
cd 31-testing-serializers && npm test
- Explore the code: Review the serializer functions and tests
- Apply to your project: Use these patterns in your own serializer testing
- Extend the patterns: Add more complex serialization scenarios