@olane/o-test
v0.7.37
Published
Comprehensive testing utilities and best practices for O-Network node development using Mocha/Chai (libp2p ecosystem)
Maintainers
Readme
@olane/o-test
Testing utilities and best practices for O-Network node development
⚠️ Important: We Use Mocha, Not Jest
Since O-Network is built on the libp2p ecosystem, we use aegir with Mocha for testing (NOT Jest).
- ✅ Use:
aegir,chai, Mocha syntax (before,after,.to.equal()) - ❌ Don't use:
jest,@types/jest,ts-jest
See MOCHA-MIGRATION.md for migration guide.
Overview
@olane/o-test provides comprehensive testing guidelines, utilities, and examples for building reliable O-Network nodes in the Olane OS ecosystem.
Quick Start
Installation
# From your package directory
pnpm install --save-dev @olane/o-test
# Install testing dependencies (aegir uses Mocha internally)
pnpm install --save-dev aegir chaiImportant: Since we're using the libp2p ecosystem, we use aegir as our test runner, which uses Mocha (not Jest). Do not install Jest dependencies.
Basic Test Structure
import 'dotenv/config';
import { describe, it, before, after } from 'mocha';
import { expect } from 'chai';
import { oLeaderNode } from '@olane/o-leader';
import { MyTool } from '../src/my-tool.tool.js';
describe('MyTool', () => {
let leaderNode: oLeaderNode;
let tool: MyTool;
before(async () => {
// Create leader
leaderNode = new oLeaderNode({
parent: null,
leader: null,
});
await leaderNode.start();
// Create tool
tool = new MyTool({
parent: leaderNode.address,
leader: leaderNode.address,
});
// Register with parent
(tool as any).hookInitializeFinished = () => {
leaderNode.addChildNode(tool);
};
await tool.start();
});
after(async () => {
await tool.stop();
await leaderNode.stop();
});
it('should start successfully', () => {
expect(tool.state).to.equal(NodeState.RUNNING);
});
it('should execute method', async () => {
const result = await tool.useSelf({
method: 'my_method',
params: { test: 'value' },
});
expect(result.success).to.be.true;
expect(result.result.data).to.exist;
});
});Documentation
📚 Complete Testing Guide
Comprehensive guide covering:
- Testing philosophy and baseline requirements
- Testing stack and configuration
- Lifecycle, method, and parent-child testing patterns
- Error handling and validation tests
- Test helpers and utilities
- CI/CD integration
- Common pitfalls and troubleshooting
🔄 Jest to Mocha Migration Guide
Quick reference for converting tests from Jest to Mocha:
- Side-by-side syntax comparison
- Complete code examples
- Common pitfalls and solutions
- Quick reference table
Baseline Requirements
Every O-Network package must have:
| Requirement | File/Location |
|-------------|---------------|
| Test directory | /test/ |
| Lifecycle test | test/lifecycle.spec.ts |
| Method tests | test/methods.spec.ts |
| Aegir config | .aegir.js (optional) |
| Test script | "test": "aegir test" in package.json |
Note: No Jest configuration needed - aegir uses Mocha internally for the libp2p ecosystem.
Minimum Test Coverage
- All nodes must have lifecycle tests (start/stop)
- All public methods (
_tool_*) must have happy path tests - All required parameters must have validation tests
- Parent-child patterns must test registration and routing
- Critical error paths must be tested
Running Tests
# Run all tests
pnpm test
# Run in watch mode
pnpm test:watch
# Run specific test file
pnpm test test/lifecycle.spec.ts
# Run with coverage
pnpm test -- --coverageConfiguration Files
.aegir.js (Optional)
Aegir works out of the box with sensible defaults. Configuration is only needed for customization:
export default {
test: {
target: ['node'], // or ['browser'], or ['node', 'browser']
},
build: {
bundlesizeMax: '100KB',
},
};Important for libp2p ecosystem:
- ✅ Aegir uses Mocha as the test runner (not Jest)
- ✅ Use Mocha syntax:
before,after,beforeEach,afterEach - ✅ Use Chai assertions:
expect().to.equal(),expect().to.exist, etc. - ❌ Do NOT install or use
jest,@types/jest, orts-jest
Critical Testing Rules
DO:
- Load environment with
import 'dotenv/config' - Use
.jsextensions in imports (ESM requirement) - Create leader node before child nodes
- Inject
hookInitializeFinishedfor parent-child registration - Clean up all nodes in
afterEach - Access response data via
result.result.data - Test both success and error paths
L DON'T:
- Override
start()method (use hooks instead) - Forget to call
stop()on nodes - Access
result.datadirectly (useresult.result.data) - Use mocks for node instances (use real nodes)
- Share mutable state between tests
Testing Philosophy
We prioritize practical, integration-oriented tests that validate real node behavior:
- Real node instances over mocks
- Actual lifecycle management
- Parent-child relationships
- Simple, focused test cases
- Integration over unit isolation
Test Patterns
Lifecycle Testing
it('should start and stop successfully', async () => {
const node = new MyTool({
parent: null,
leader: null,
});
await node.start();
expect(node.state).to.equal(NodeState.RUNNING);
await node.stop();
expect(node.state).to.equal(NodeState.STOPPED);
});Method Testing
it('should validate required parameters', async () => {
const result = await tool.useSelf({
method: 'my_method',
params: {}, // Missing required params
});
expect(result.success).to.be.false;
expect(result.error).to.include('required');
});Parent-Child Testing
it('should create and route to child', async () => {
// Create child
const createResult = await manager.useSelf({
method: 'create_worker',
params: { workerId: 'worker-1' },
});
expect(createResult.success).to.be.true;
// Route to child
const routeResult = await manager.useSelf({
method: 'use_worker',
params: {
workerId: 'worker-1',
method: 'process_task',
params: { data: 'test' },
},
});
expect(routeResult.success).to.be.true;
});Common Pitfalls
Pitfall 1: Missing Hook Injection
// L WRONG
const tool = new MyTool({ parent: leader.address, leader: leader.address });
await tool.start(); // Child not registered!
// CORRECT
const tool = new MyTool({ parent: leader.address, leader: leader.address });
(tool as any).hookInitializeFinished = () => {
leaderNode.addChildNode(tool);
};
await tool.start();Pitfall 2: No Cleanup
// L WRONG
it('test', async () => {
const tool = new MyTool({});
await tool.start();
// No cleanup - nodes leak!
});
// CORRECT
afterEach(async () => {
if (tool) await tool.stop();
if (leader) await leader.stop();
});Pitfall 3: Wrong Response Access
// L WRONG
const data = result.data; // undefined!
// CORRECT
const data = result.result.data;Test Helpers
Create shared utilities for common patterns:
// test/helpers/test-utils.ts
export async function createToolWithLeader<T>(
ToolClass: new (config: any) => T,
config: any = {}
): Promise<{ leader: oLeaderNode; tool: T }> {
const leader = new oLeaderNode({ parent: null, leader: null });
await leader.start();
const tool = new ToolClass({
...config,
parent: leader.address,
leader: leader.address,
});
(tool as any).hookInitializeFinished = () => {
leader.addChildNode(tool as any);
};
await (tool as any).start();
return { leader, tool };
}Example Tests
See the test/ directory for complete examples:
test/lifecycle.spec.ts- Node lifecycle testingtest/methods.spec.ts- Method validation and executiontest/parent-child.spec.ts- Manager/worker pattern testingtest/helpers/- Shared test utilities
Resources
- TESTING.md - Complete testing guide
- CLAUDE.md - O-Network node development guide
- Olane Documentation - Full ecosystem docs
Package Structure
o-test/
�� src/
�� index.ts # Public exports
�� example-tool.tool.ts # Example tool implementation
�� methods/
�� example.methods.ts # Method definitions
�� test/
�� lifecycle.spec.ts # Lifecycle tests
�� methods.spec.ts # Method tests
�� parent-child.spec.ts # Parent-child tests
�� helpers/
�� test-utils.ts # Test utilities
�� fixtures/
�� mock-data.ts # Test data
�� jest.config.js # Jest configuration
�� .aegir.js # Aegir configuration
�� tsconfig.json # TypeScript configuration
�� package.json # Package metadata
�� README.md # This file
�� TESTING.md # Complete testing guide
�� CLAUDE.md # Development guideContributing
When adding tests:
- Follow the patterns in TESTING.md
- Ensure all baseline requirements are met
- Test both success and error paths
- Clean up all resources
- Use descriptive test names
License
See the root LICENSE file in the Olane monorepo.
Support
- GitHub Issues: olane/issues
- Documentation: docs.olane.ai
- Community: Discord
Remember:
- Real nodes, not mocks
- Proper lifecycle management
- Clean up in
afterEach - Test integration, not isolation
- Keep it simple
For detailed testing patterns and examples, see TESTING.md.
