npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@message-in-the-middle/testing

v0.1.3

Published

Testing utilities and mocks for message-middleware

Readme

@message-in-the-middle/testing

⚠️ Work in Progress Is this library production-ready? No. Is this library safe? No. When will it be ready? Soon™ (maybe tomorrow, maybe never). Why is it public? Experiment

message-in-the-middle is to Express.js what your message queue processing is to HTTP request processing. Just as Express provides a middleware pattern for HTTP requests, this library provides a middleware pattern for processing queue messages.

Why This Exists

Processing queue messages usually means copy-pasting the same boilerplate: parse JSON, validate, log, retry, deduplicate, route to handlers. This library lets you compose that logic as middlewares.


Testing utilities and mocks for message-middleware. Simplify unit and integration testing of message processing pipelines.

This package provides test helpers, mocks, and utilities to make testing middleware and message processing logic straightforward and reliable.

Features

  • 🧪 Test Harness - Test middleware in isolation
  • 🎭 Mock Store - In-memory message store with spy capabilities
  • 🛠️ Test Utilities - Helper functions for creating test contexts
  • 🎯 Type-Safe - Full TypeScript support
  • 🚀 Framework Agnostic - Works with any test framework (Vitest, Jest, Mocha)

Installation

# npm
npm install --save-dev @message-in-the-middle/testing

# pnpm
pnpm add -D @message-in-the-middle/testing

# yarn
yarn add --dev @message-in-the-middle/testing

Note: This is a dev dependency. Don't use in production code.

Quick Start

Basic Test with Test Utils

import { describe, it, expect } from 'vitest';
import { createMockContext } from '@message-in-the-middle/testing';
import { ParseJsonInboundMiddleware } from '@message-in-the-middle/core';

describe('ParseJsonInboundMiddleware', () => {
  it('should parse JSON string', async () => {
    const middleware = new ParseJsonInboundMiddleware();
    const context = createMockContext({
      message: '{"order": 123}'
    });

    await middleware.process(context, async () => {});

    expect(context.message).toEqual({ order: 123 });
  });
});

Test Utilities

createMockContext()

Create a mock message context for testing:

import { createMockContext } from '@message-in-the-middle/testing';

// Minimal context
const context = createMockContext();

// With message
const context = createMockContext({
  message: { order: 123 }
});

// With all fields
const context = createMockContext({
  message: { order: 123 },
  raw: { MessageId: 'abc-123' },
  metadata: { source: 'sqs' },
  attributes: { timestamp: Date.now() }
});

createMockMessage()

Create a mock message for testing:

import { createMockMessage } from '@message-in-the-middle/testing';

// Simple message
const message = createMockMessage({
  action: 'CREATE_ORDER',
  orderId: '123'
});

// With metadata
const message = createMockMessage(
  { action: 'CREATE_ORDER' },
  { source: 'api' }
);

Middleware Test Harness

Test middleware in isolation with a simple harness:

import { describe, it, expect } from 'vitest';
import { MiddlewareTestHarness } from '@message-in-the-middle/testing';
import { ValidateInboundMiddleware } from '@message-in-the-middle/core';

describe('ValidateInboundMiddleware', () => {
  it('should validate message', async () => {
    const validator = async (msg) => {
      if (!msg.orderId) throw new Error('Missing orderId');
      return msg;
    };

    const middleware = new ValidateInboundMiddleware(validator);
    const harness = new MiddlewareTestHarness(middleware);

    // Valid message - should not throw
    await harness.execute({
      message: { orderId: '123', amount: 100 }
    });

    // Invalid message - should throw
    await expect(harness.execute({
      message: { amount: 100 }
    })).rejects.toThrow('Missing orderId');
  });
});

Test Harness API

class MiddlewareTestHarness<T = any> {
  constructor(middleware: InboundMiddleware<T>);

  // Execute middleware with context
  async execute(context: Partial<MessageContext<T>>): Promise<MessageContext<T>>;

  // Get execution count
  getExecutionCount(): number;

  // Reset harness
  reset(): void;
}

Mock Message Store

Mock implementation of MessageStore with spy capabilities:

import { describe, it, expect } from 'vitest';
import { MockMessageStore } from '@message-in-the-middle/testing';
import { PersistenceInboundMiddleware, MessageStatus } from '@message-in-the-middle/persistence-core';

describe('PersistenceInboundMiddleware', () => {
  it('should store failed messages', async () => {
    const store = new MockMessageStore();
    const middleware = new PersistenceInboundMiddleware(store, {
      storeOn: ['error']
    });

    const context = createMockContext({
      message: { order: 123 }
    });

    // Simulate error
    try {
      await middleware.process(context, async () => {
        throw new Error('Processing failed');
      });
    } catch (error) {
      // Expected
    }

    // Verify message was stored
    expect(store.getSaveCount()).toBe(1);
    const saved = store.getAllMessages();
    expect(saved).toHaveLength(1);
    expect(saved[0].status).toBe(MessageStatus.FAILED);
  });
});

Mock Store API

class MockMessageStore implements MessageStore {
  // Standard MessageStore methods
  save(message: StoredMessage): Promise<void>;
  findById(id: string): Promise<StoredMessage | null>;
  findByStatus(status: MessageStatus, options?: QueryOptions): Promise<StoredMessage[]>;
  // ... other MessageStore methods

  // Spy methods
  getSaveCount(): number;
  getUpdateCount(): number;
  getDeleteCount(): number;
  getAllMessages(): StoredMessage[];
  getMessageById(id: string): StoredMessage | undefined;
  reset(): void;
}

Testing Patterns

Pattern 1: Test Middleware in Isolation

import { describe, it, expect } from 'vitest';
import { MiddlewareTestHarness, createMockContext } from '@message-in-the-middle/testing';
import { LogInboundMiddleware } from '@message-in-the-middle/core';

describe('LogInboundMiddleware', () => {
  it('should log message processing', async () => {
    const logs: string[] = [];
    const logger = {
      log: (msg: string) => logs.push(msg)
    };

    const middleware = new LogInboundMiddleware(logger);
    const harness = new MiddlewareTestHarness(middleware);

    await harness.execute({
      message: { order: 123 }
    });

    expect(logs.length).toBeGreaterThan(0);
    expect(logs[0]).toContain('Processing message');
  });
});

Pattern 2: Test Custom Middleware

import { describe, it, expect, beforeEach } from 'vitest';
import { createMockContext } from '@message-in-the-middle/testing';
import { InboundMiddleware, MessageContext } from '@message-in-the-middle/core';

class CustomEnrichmentMiddleware implements InboundMiddleware {
  async process(ctx: MessageContext, next: () => Promise<void>): Promise<void> {
    ctx.metadata.enriched = true;
    ctx.metadata.timestamp = Date.now();
    await next();
  }
}

describe('CustomEnrichmentMiddleware', () => {
  let middleware: CustomEnrichmentMiddleware;

  beforeEach(() => {
    middleware = new CustomEnrichmentMiddleware();
  });

  it('should enrich message with metadata', async () => {
    const context = createMockContext({
      message: { order: 123 }
    });

    let nextCalled = false;
    await middleware.process(context, async () => {
      nextCalled = true;
    });

    expect(nextCalled).toBe(true);
    expect(context.metadata.enriched).toBe(true);
    expect(context.metadata.timestamp).toBeDefined();
  });
});

Pattern 3: Test Pipeline Integration

import { describe, it, expect } from 'vitest';
import { MockMessageStore, createMockContext } from '@message-in-the-middle/testing';
import {
  MessageMiddlewareManager,
  ParseJsonInboundMiddleware,
  ValidateInboundMiddleware
} from '@message-in-the-middle/core';
import { PersistenceInboundMiddleware, MessageStatus } from '@message-in-the-middle/persistence-core';

describe('Message Processing Pipeline', () => {
  it('should process valid message through pipeline', async () => {
    const store = new MockMessageStore();
    const manager = new MessageMiddlewareManager();

    manager
      .addInboundMiddleware(new ParseJsonInboundMiddleware())
      .addInboundMiddleware(new ValidateInboundMiddleware(async (msg) => {
        if (!msg.orderId) throw new Error('Missing orderId');
        return msg;
      }))
      .addInboundMiddleware(new PersistenceInboundMiddleware(store, {
        storeOn: ['always']
      }));

    // Process valid message
    const result = await manager.processInbound(
      JSON.stringify({ orderId: '123', amount: 100 })
    );

    expect(result.message).toEqual({ orderId: '123', amount: 100 });
    expect(store.getSaveCount()).toBe(1);

    const saved = store.getAllMessages();
    expect(saved[0].status).toBe(MessageStatus.SUCCEEDED);
  });

  it('should handle validation errors', async () => {
    const store = new MockMessageStore();
    const manager = new MessageMiddlewareManager();

    manager
      .addInboundMiddleware(new ParseJsonInboundMiddleware())
      .addInboundMiddleware(new ValidateInboundMiddleware(async (msg) => {
        if (!msg.orderId) throw new Error('Missing orderId');
        return msg;
      }))
      .addInboundMiddleware(new PersistenceInboundMiddleware(store, {
        storeOn: ['error']
      }));

    // Process invalid message
    await expect(
      manager.processInbound(JSON.stringify({ amount: 100 }))
    ).rejects.toThrow('Missing orderId');

    expect(store.getSaveCount()).toBe(1);
    const saved = store.getAllMessages();
    expect(saved[0].status).toBe(MessageStatus.FAILED);
    expect(saved[0].errorMessage).toContain('Missing orderId');
  });
});

Pattern 4: Test Message Handlers

import { describe, it, expect } from 'vitest';
import { createMockContext } from '@message-in-the-middle/testing';
import { MessageContext } from '@message-in-the-middle/core';

async function handleCreateOrder(ctx: MessageContext): Promise<void> {
  const { orderId, amount } = ctx.message;

  // Business logic
  if (amount <= 0) {
    throw new Error('Invalid amount');
  }

  ctx.metadata.orderCreated = true;
  ctx.metadata.orderId = orderId;
}

describe('handleCreateOrder', () => {
  it('should handle valid order', async () => {
    const context = createMockContext({
      message: { orderId: '123', amount: 100 }
    });

    await handleCreateOrder(context);

    expect(context.metadata.orderCreated).toBe(true);
    expect(context.metadata.orderId).toBe('123');
  });

  it('should reject invalid amount', async () => {
    const context = createMockContext({
      message: { orderId: '123', amount: -50 }
    });

    await expect(handleCreateOrder(context)).rejects.toThrow('Invalid amount');
  });
});

Pattern 5: Test with Message Dispatcher

import { describe, it, expect } from 'vitest';
import { createMockContext } from '@message-in-the-middle/testing';
import { MessageDispatcher, MessageContext } from '@message-in-the-middle/core';

describe('MessageDispatcher', () => {
  it('should route messages to correct handler', async () => {
    const dispatcher = new MessageDispatcher({ identifierField: 'action' });

    const handlerCalls: string[] = [];

    dispatcher.register('CREATE_ORDER', async (ctx: MessageContext) => {
      handlerCalls.push('create');
    });

    dispatcher.register('UPDATE_ORDER', async (ctx: MessageContext) => {
      handlerCalls.push('update');
    });

    // Test CREATE_ORDER
    const createContext = createMockContext({
      message: { action: 'CREATE_ORDER', orderId: '123' }
    });
    await dispatcher.dispatch(createContext);
    expect(handlerCalls).toEqual(['create']);

    // Test UPDATE_ORDER
    const updateContext = createMockContext({
      message: { action: 'UPDATE_ORDER', orderId: '123' }
    });
    await dispatcher.dispatch(updateContext);
    expect(handlerCalls).toEqual(['create', 'update']);
  });
});

Test Framework Examples

Vitest

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { createMockContext, MockMessageStore } from '@message-in-the-middle/testing';

describe('My Feature', () => {
  let store: MockMessageStore;

  beforeEach(() => {
    store = new MockMessageStore();
  });

  afterEach(() => {
    store.reset();
  });

  it('should work', async () => {
    // Test code
  });
});

Jest

import { createMockContext, MockMessageStore } from '@message-in-the-middle/testing';

describe('My Feature', () => {
  let store: MockMessageStore;

  beforeEach(() => {
    store = new MockMessageStore();
  });

  afterEach(() => {
    store.reset();
  });

  test('should work', async () => {
    // Test code
  });
});

Mocha + Chai

import { expect } from 'chai';
import { createMockContext, MockMessageStore } from '@message-in-the-middle/testing';

describe('My Feature', () => {
  let store: MockMessageStore;

  beforeEach(() => {
    store = new MockMessageStore();
  });

  afterEach(() => {
    store.reset();
  });

  it('should work', async () => {
    // Test code
  });
});

Best Practices

  1. Use Test Harness for Middleware - Test middleware in isolation before integration
  2. Use Mock Store for Persistence - Avoid real database in unit tests
  3. Reset Mocks - Always reset mocks between tests
  4. Test Error Cases - Test both success and failure paths
  5. Keep Tests Fast - Use mocks and in-memory stores for speed

API Reference

Test Utilities

// Create mock context
function createMockContext<T = any>(
  partial?: Partial<MessageContext<T>>
): MessageContext<T>

// Create mock message
function createMockMessage<T = any>(
  message: T,
  metadata?: Record<string, any>
): MessageContext<T>

Middleware Test Harness

class MiddlewareTestHarness<T = any> {
  constructor(middleware: InboundMiddleware<T>)
  execute(context: Partial<MessageContext<T>>): Promise<MessageContext<T>>
  getExecutionCount(): number
  reset(): void
}

Mock Message Store

class MockMessageStore implements MessageStore {
  // MessageStore interface methods
  save(message: StoredMessage): Promise<void>
  findById(id: string): Promise<StoredMessage | null>
  findByStatus(status: MessageStatus, options?: QueryOptions): Promise<StoredMessage[]>
  findByError(errorType: string, options?: QueryOptions): Promise<StoredMessage[]>
  updateStatus(id: string, updates: Partial<StoredMessage>): Promise<void>
  count(filters?: QueryFilters): Promise<number>
  delete(id: string): Promise<void>
  destroy(): Promise<void>

  // Spy methods
  getSaveCount(): number
  getUpdateCount(): number
  getDeleteCount(): number
  getAllMessages(): StoredMessage[]
  getMessageById(id: string): StoredMessage | undefined
  reset(): void
}

Related Packages

Documentation

License

MIT

Links