@soapjs/integr8-express
v1.0.0
Published
Express.js integration for @soapjs/integr8 testing framework
Maintainers
Readme
@soapjs/integr8-express
Express.js integration for @soapjs/integr8 testing framework.
Installation
npm install @soapjs/integr8 @soapjs/integr8-expressQuick Start
// app.ts
import express from 'express';
import { bootstrapExpressIntegr8 } from '@soapjs/integr8-express';
async function start() {
const app = express();
// Your Express setup
app.use(express.json());
app.use('/users', userRoutes);
// Enable Integr8 in test mode
if (process.env.INTEGR8_MODE === 'true') {
await bootstrapExpressIntegr8(app, {
port: 3000,
enableTestEndpoints: true
});
}
app.listen(3000);
}
start();Usage in Tests
import { setupEnvironment, getEnvironmentContext } from '@soapjs/integr8';
test('should mock service', async () => {
const ctx = getEnvironmentContext();
await ctx.getCtx().override.service('UserService').withMock({
findById: async (id) => ({ id, name: 'Mock User' })
});
const response = await ctx.getHttp().get('/users/123');
expect(response.data.name).toBe('Mock User');
});Features
- ✅ Automatic Test Endpoints -
/__integr8__/override,/__integr8__/health,/__integr8__/reset - ✅ Service Mocking - Support for singleton pattern and DI containers
- ✅ Middleware Mocking - Override Express middleware at runtime
- ✅ Built-in DI Container - Optional simple dependency injection container
- ✅ TypeScript Support - Full type definitions included
- ✅ Zero Configuration - Works out of the box for basic use cases
Core Components
bootstrapExpressIntegr8
Bootstrap your Express application with Integr8 testing capabilities:
import { bootstrapExpressIntegr8 } from '@soapjs/integr8-express';
await bootstrapExpressIntegr8(app, {
port: 3000,
enableTestEndpoints: true,
adapterOptions: {
logger: console // Optional logger
}
});ExpressAdapter
Express adapter implementing Integr8's Adapter interface with enhanced override capabilities:
import { ExpressAdapter } from '@soapjs/integr8-express';
const adapter = new ExpressAdapter();
// Initialize with Express app
await adapter.initialize({
type: 'express',
config: {
app: expressApp,
logging: 'debug' // optional
}
});
// Apply overrides
await adapter.applyOverride('service', 'UserService', mockService);
await adapter.applyOverride('middleware', 'authMiddleware', mockMiddleware);
await adapter.applyOverride('provider', 'EmailProvider', mockProvider);
// Check active overrides
const overrides = adapter.getOverrides(); // Returns: ['service:UserService', 'middleware:authMiddleware', ...]
// Clear all overrides (restores original middleware)
await adapter.clearOverrides();
// Teardown
await adapter.teardown();Override Strategies:
The adapter uses multiple strategies to find and override services:
- Service Registry - If
app.locals.serviceRegistryexists with anoverride()method - Setter Pattern - Looks for
setXxxService()functions in common service paths - App Locals Storage - Falls back to storing in
app.locals.integr8Services
Supported Override Types:
service- Business logic servicesmiddleware- Express middleware functionsprovider- Data providers (databases, APIs, etc.)route- Route handlers (experimental)
SimpleContainer
Optional dependency injection container:
import { SimpleContainer } from '@soapjs/integr8-express';
const container = new SimpleContainer();
// Register services
container.register('UserRepository', () => new UserRepository());
container.register('UserService', (c) => {
return new UserService(c.get('UserRepository'));
});
// Get service instance (singleton by default)
const service = container.get('UserService');
// Override for testing
container.override('UserService', mockService);
// Reset
container.reset(); // Reset all
container.reset('UserService'); // Reset specific serviceTest Endpoints
When enableTestEndpoints is true, the following endpoints are available:
GET /integr8/health
Health check with override information:
curl http://localhost:3000/__integr8__/healthResponse:
{
"status": "ok",
"timestamp": "2025-10-09T12:00:00.000Z",
"integr8": true,
"overrides": ["service:UserService", "middleware:auth"]
}POST /integr8/override
Apply runtime override:
curl -X POST http://localhost:3000/__integr8__/override \
-H "Content-Type: application/json" \
-d '{
"type": "service",
"name": "UserService",
"implementation": { "findById": "..." }
}'POST /integr8/reset
Clear all overrides:
curl -X POST http://localhost:3000/__integr8__/resetService Patterns
Singleton Pattern (without DI)
// services/user.service.ts
let instance: UserService | null = null;
export function getUserService(): UserService {
if (!instance) instance = new UserService();
return instance;
}
export function setUserService(service: UserService): void {
instance = service;
}
// routes/users.ts
import { getUserService } from '../services/user.service';
router.get('/:id', async (req, res) => {
const service = getUserService();
const user = await service.findById(req.params.id);
res.json(user);
});With DI Container
// container.ts
import { SimpleContainer } from '@soapjs/integr8-express';
const container = new SimpleContainer();
container.register('UserService', (c) =>
new UserService(c.get('UserRepository'))
);
// app.ts
app.locals.serviceRegistry = container;
// routes/users.ts
router.get('/:id', async (req, res) => {
const service = req.app.locals.serviceRegistry.get('UserService');
const user = await service.findById(req.params.id);
res.json(user);
});Examples
The package includes three complete examples:
1. Basic Example
Simple Express app with singleton pattern
- No DI container
- Setter pattern for testing
- Basic service mocking
2. With Container Example
Express app with built-in DI container
- Service-Repository pattern
- Automatic dependency resolution
- Easy service mocking
3. Advanced Example
Production-ready Express app
- Multiple services with dependencies
- Authentication middleware
- Caching layer
- Error handling
- Request logging
API Reference
Types
interface BootstrapOptions {
port?: number;
enableTestEndpoints?: boolean;
adapterOptions?: AdapterOptions;
}
interface AdapterOptions {
app?: Application;
logger?: Logger;
[key: string]: any;
}
interface Logger {
debug?(message: string, ...args: any[]): void;
info?(message: string, ...args: any[]): void;
warn?(message: string, ...args: any[]): void;
error?(message: string, ...args: any[]): void;
}Best Practices
- Use DI Container - For better testability and cleaner code
- Enable Test Mode Conditionally - Only in test environment
- Clear Overrides - Reset state between tests
- Type Your Services - Use TypeScript for better IDE support
- Organize by Layer - Separate routes, services, and repositories
Testing Examples
Mock Service
test('should mock UserService', async () => {
const ctx = getEnvironmentContext();
await ctx.getCtx().override.service('UserService').withMock({
findById: async (id) => ({ id, name: 'Mock' })
});
const response = await ctx.getHttp().get('/users/123');
expect(response.data.name).toBe('Mock');
});Mock Middleware
test('should bypass auth middleware', async () => {
const ctx = getEnvironmentContext();
await ctx.getCtx().override.middleware('authMiddleware').with(
(req, res, next) => {
req.user = { id: 'test', roles: ['admin'] };
next();
}
);
const response = await ctx.getHttp().get('/admin/users');
expect(response.status).toBe(200);
});Reset Between Tests
beforeEach(async () => {
const ctx = getEnvironmentContext();
await ctx.getAdapter().clearOverrides();
});Architecture
Adapter Design
The ExpressAdapter follows the same design pattern as the NestJS adapter in @soapjs/integr8:
interface Adapter {
name: string;
initialize(config: AdapterConfig): Promise<void>;
applyOverride(type: string, name: string, implementation: any): Promise<void>;
teardown(): Promise<void>;
}Key Features:
- Runtime Override System - Modifies services, middleware, and providers at runtime
- Multiple DI Strategies - Works with or without dependency injection containers
- Middleware Preservation - Saves original middleware for restoration
- Comprehensive Logging - Integrates with Integr8's logging system
Comparison with NestJS Adapter:
| Feature | NestJS Adapter | Express Adapter | |---------|----------------|-----------------| | DI System | ModuleRef (built-in) | Optional (SimpleContainer or custom) | | Override Target | Providers, Guards, Interceptors | Services, Middleware, Providers | | Resolution Strategy | Module hierarchy | Registry → Setter → Locals | | State Management | Container-based | Map-based with fallbacks |
How Overrides Work
Service Override Flow:
1. User calls adapter.applyOverride('service', 'UserService', mockImpl)
2. Adapter stores override in Map
3. Adapter tries resolution strategies:
a. Check app.locals.serviceRegistry.override()
b. Look for setUserService() function
c. Store in app.locals.integr8Services
4. Application code retrieves overridden serviceMiddleware Override Flow:
1. User calls adapter.applyOverride('middleware', 'auth', mockMiddleware)
2. Adapter saves original middleware
3. Adapter finds middleware in Express router stack
4. Replaces layer.handle with new implementation
5. On clearOverrides(), restores originalCompatibility
- Express: ^4.0.0
- @soapjs/integr8: ^0.2.1
- TypeScript: ^5.0.0
- Node.js: >=16.0.0
License
MIT © SoapJS
Links
- Main Documentation
- Adapter Documentation - Detailed adapter implementation guide
- Quick Start Guide
- Examples
- Integr8 Core Documentation
- Issues
- NPM Package
