Package Exports
- @soapjs/integr8-express
- @soapjs/integr8-express/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@soapjs/integr8-express) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
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