JSPM

@soapjs/integr8-express

1.0.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 1
  • Score
    100M100P100Q14646F
  • License MIT

Express.js integration for @soapjs/integr8 testing framework

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-express

Quick 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:

  1. Service Registry - If app.locals.serviceRegistry exists with an override() method
  2. Setter Pattern - Looks for setXxxService() functions in common service paths
  3. App Locals Storage - Falls back to storing in app.locals.integr8Services

Supported Override Types:

  • service - Business logic services
  • middleware - Express middleware functions
  • provider - 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 service

Test Endpoints

When enableTestEndpoints is true, the following endpoints are available:

GET /integr8/health

Health check with override information:

curl http://localhost:3000/__integr8__/health

Response:

{
  "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__/reset

Service 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

View example

2. With Container Example

Express app with built-in DI container

  • Service-Repository pattern
  • Automatic dependency resolution
  • Easy service mocking

View example

3. Advanced Example

Production-ready Express app

  • Multiple services with dependencies
  • Authentication middleware
  • Caching layer
  • Error handling
  • Request logging

View example

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

  1. Use DI Container - For better testability and cleaner code
  2. Enable Test Mode Conditionally - Only in test environment
  3. Clear Overrides - Reset state between tests
  4. Type Your Services - Use TypeScript for better IDE support
  5. 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:

  1. Runtime Override System - Modifies services, middleware, and providers at runtime
  2. Multiple DI Strategies - Works with or without dependency injection containers
  3. Middleware Preservation - Saves original middleware for restoration
  4. 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 service

Middleware 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 original

Compatibility

  • Express: ^4.0.0
  • @soapjs/integr8: ^0.2.1
  • TypeScript: ^5.0.0
  • Node.js: >=16.0.0

License

MIT © SoapJS