Skip to content

Proxy Mode Examples ​

Practical examples for using Testurio's proxy mode to intercept and modify traffic between clients and servers.

How Proxy Mode Works ​

When an AsyncServer (or Server) has both listenAddress and targetAddress, it acts as a transparent proxy:

Client ──→ Proxy (listenAddress) ──→ Backend (targetAddress)
Client ←── Proxy ←────────────────── Backend

Hooks on the proxy can inspect, transform, or mock messages passing through.

Setup ​

typescript
import { AsyncClient, AsyncServer, TestScenario, testCase } from 'testurio';
import { TcpProtocol, type TcpServiceDefinition } from '@testurio/protocol-tcp';

interface ProxyService extends TcpServiceDefinition {
  clientMessages: {
    Request: { id: string; action: string; data: string };
  };
  serverMessages: {
    Response: { id: string; result: string; timestamp: number };
  };
}

// Real backend server
const backend = new AsyncServer('backend', {
  protocol: new TcpProtocol<ProxyService>(),
  listenAddress: { host: 'localhost', port: 9100 },
});

// Proxy server — both listenAddress AND targetAddress
const proxy = new AsyncServer('proxy', {
  protocol: new TcpProtocol<ProxyService>(),
  listenAddress: { host: 'localhost', port: 9101 },
  targetAddress: { host: 'localhost', port: 9100 }, // forwards to backend
});

// Client connects to proxy, not backend
const client = new AsyncClient('client', {
  protocol: new TcpProtocol<ProxyService>(),
  targetAddress: { host: 'localhost', port: 9101 }, // connects to proxy
});

const scenario = new TestScenario({
  name: 'Proxy Mode',
  components: [backend, proxy, client],
});

Passthrough ​

Messages flow through the proxy unmodified:

typescript
const tc = testCase('Passthrough mode', (test) => {
  const api = test.use(client);
  const mock = test.use(backend);

  mock.onMessage('Request').mockEvent('Response', (payload) => ({
    id: payload.id,
    result: `Processed: ${payload.action}`,
    timestamp: Date.now(),
  }));

  api.sendMessage('Request', { id: 'req-1', action: 'get', data: 'item-123' });

  api.waitEvent('Response').timeout(2000).assert((msg) => {
    return msg.id === 'req-1' && msg.result.includes('Processed');
  });
});

Message Inspection ​

Observe messages at the proxy without modifying them:

typescript
const tc = testCase('Message inspection', (test) => {
  const api = test.use(client);
  const proxyServer = test.use(proxy);
  const mock = test.use(backend);

  mock.onMessage('Request').mockEvent('Response', (payload) => ({
    id: payload.id,
    result: 'success',
    timestamp: Date.now(),
  }));

  // Proxy observes the message passing through
  proxyServer.waitMessage('Request').timeout(2000).assert((msg) => {
    return msg.action === 'inspect-me';
  });

  api.sendMessage('Request', {
    id: 'req-2',
    action: 'inspect-me',
    data: 'sensitive-data',
  });

  api.waitEvent('Response').timeout(2000).assert((msg) => msg.id === 'req-2');
});

Message Transformation ​

Modify messages as they pass through the proxy:

typescript
const tc = testCase('Message transformation', (test) => {
  const api = test.use(client);
  const proxyServer = test.use(proxy);
  const mock = test.use(backend);

  // Proxy transforms outgoing requests
  proxyServer.onMessage('Request').transform((msg) => ({
    ...msg,
    data: `[TRANSFORMED] ${msg.data}`,
  }));

  // Backend receives the transformed message
  mock.onMessage('Request').mockEvent('Response', (payload) => ({
    id: payload.id,
    result: payload.data, // echoes transformed data
    timestamp: Date.now(),
  }));

  api.sendMessage('Request', { id: 'req-3', action: 'transform', data: 'original' });

  api.waitEvent('Response').timeout(2000).assert((msg) => {
    return msg.result.includes('[TRANSFORMED]');
  });
});

HTTP Proxy Mode ​

Proxy mode also works with sync protocols:

typescript
import { Client, HttpProtocol, Server, TestScenario, testCase } from 'testurio';

interface ApiService {
  getUser: {
    request: { method: 'GET'; path: '/users/{id}' };
    response: { code: 200; body: { id: number; name: string } };
  };
}

const backend = new Server('backend', {
  protocol: new HttpProtocol<ApiService>(),
  listenAddress: { host: 'localhost', port: 3000 },
});

// HTTP proxy — listenAddress + targetAddress
const proxy = new Server('proxy', {
  protocol: new HttpProtocol<ApiService>(),
  listenAddress: { host: 'localhost', port: 3001 },
  targetAddress: { host: 'localhost', port: 3000 },
});

const client = new Client('api', {
  protocol: new HttpProtocol<ApiService>(),
  targetAddress: { host: 'localhost', port: 3001 }, // connects to proxy
});

const scenario = new TestScenario({
  name: 'HTTP Proxy',
  components: [backend, proxy, client],
});

const tc = testCase('HTTP proxy passthrough', (test) => {
  const api = test.use(client);
  const mock = test.use(backend);

  api.request('getUser', { method: 'GET', path: '/users/1' });

  mock.onRequest('getUser').mockResponse(() => ({
    code: 200,
    body: { id: 1, name: 'Alice' },
  }));

  api.onResponse('getUser').assert((res) => res.body.name === 'Alice');
});

Selective Mocking ​

Mock some requests at the proxy, let others pass through:

typescript
const tc = testCase('Selective mocking', (test) => {
  const api = test.use(client);
  const proxyServer = test.use(proxy);
  const mock = test.use(backend);

  // Mock specific requests at the proxy level
  proxyServer.onRequest('getUser', { method: 'GET', path: '/users/99' }).mockResponse(() => ({
    code: 200,
    body: { id: 99, name: 'Mocked User' },
  }));

  // Other requests pass through to the real backend
  mock.onRequest('getUser').mockResponse(() => ({
    code: 200,
    body: { id: 1, name: 'Real User' },
  }));
});

Released under the MIT License.