npm package: @soapjs/integr8-express
Description: Express.js integration for @soapjs/integr8 testing framework
Author: SoapJS
Latest version: 1.0.0

# @soapjs/integr8-express

Express.js integration for [@soapjs/integr8](https://github.com/soapjs/node-integr8) testing framework.

## Installation

```bash
npm install @soapjs/integr8 @soapjs/integr8-express
```

## Quick Start

```typescript
// 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

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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:

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

Response:
```json
{
  "status": "ok",
  "timestamp": "2025-10-09T12:00:00.000Z",
  "integr8": true,
  "overrides": ["service:UserService", "middleware:auth"]
}
```

### POST /__integr8__/override

Apply runtime override:

```bash
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:

```bash
curl -X POST http://localhost:3000/__integr8__/reset
```

## Service Patterns

### Singleton Pattern (without DI)

```typescript
// 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

```typescript
// 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](./examples/basic/)

### 2. With Container Example
Express app with built-in DI container
- Service-Repository pattern
- Automatic dependency resolution
- Easy service mocking

[View example](./examples/with-container/)

### 3. Advanced Example
Production-ready Express app
- Multiple services with dependencies
- Authentication middleware
- Caching layer
- Error handling
- Request logging

[View example](./examples/advanced/)

## API Reference

### Types

```typescript
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

```typescript
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

```typescript
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

```typescript
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`:

```typescript
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](https://github.com/soapjs)

## Links

- [Main Documentation](./README.md)
- [Adapter Documentation](./ADAPTER.md) - Detailed adapter implementation guide
- [Quick Start Guide](./QUICKSTART.md)
- [Examples](./examples)
- [Integr8 Core Documentation](https://github.com/soapjs/node-integr8)
- [Issues](https://github.com/soapjs/integr8-express/issues)
- [NPM Package](https://www.npmjs.com/package/@soapjs/integr8-express)