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

@jamx-framework/testing

v1.0.0

Published

JAMX Framework — Testing utilities

Downloads

265

Readme

@jamx-framework/testing

Descripción

Utilidades de testing para JAMX Framework. Proporciona herramientas para tests de integración y unitarios, incluyendo un cliente HTTP, un servidor HTTP minimalista, factories de objetos, y mocks de los servicios principales de JAMX (mailer, storage, cache, queue).

Cómo funciona

El paquete ofrece tres categorías de herramientas:

  1. TestClient: Cliente HTTP para realizar requests a servidores reales (ideal para tests de integración)
  2. TestServer: Servidor HTTP minimalista que corre en memoria para simular endpoints
  3. Factories: Funciones para crear objetos de test con datos predeterminados y overrides
  4. Mocks: Implementaciones en memoria de servicios JAMX para aislar tests

Componentes principales

TestClient (src/test-client.ts)

Cliente HTTP que realiza requests reales a un servidor:

  • get(path, options): Realiza petición GET
  • post(path, options): Realiza petición POST con body JSON
  • put(path, options): Realiza petición PUT
  • patch(path, options): Realiza petición PATCH
  • delete(path, options): Realiza petición DELETE

Retorna TestResponse con métodos json<T>() y text().

TestServer (src/test-server.ts)

Servidor HTTP minimalista para tests:

  • get/post/put/delete(path, handler): Define rutas
  • use(handler): Middleware global
  • start(): Inicia el servidor en puerto aleatorio
  • stop(): Detiene el servidor
  • url: URL base del servidor (ej: http://localhost:3000)

Los handlers reciben (req, res) donde req tiene body y params parseados.

Factories (src/factories.ts)

  • createFactory<T>(defaults): Crea una factory de objetos
  • createSequence<T>(fn): Crea un generador de valores secuenciales

Mocks

  • mockMailer(): Mock del servicio de email
  • mockStorage(baseUrl?): Mock del storage (S3/local)
  • mockCache(prefix?): Mock del cache (Redis/memory)
  • mockQueue<T>(handler?): Mock de cola de jobs

Uso básico

TestClient

import { TestClient } from '@jamx-framework/testing';

const client = new TestClient('http://localhost:3000');

// GET request
const response = await client.get('/api/users');
const users = response.json<User[]>();

// POST request
const createResponse = await client.post('/api/users', {
  body: { name: 'Alice', email: '[email protected]' },
  headers: { Authorization: 'Bearer token' },
});
const newUser = createResponse.json<User>();

TestServer

import { createTestServer } from '@jamx-framework/testing';

const server = createTestServer();

// Definir rutas
server.get('/api/users', (req, res) => {
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify([{ id: 1, name: 'Alice' }]));
});

server.post('/api/users', async (req, res) => {
  const user = req.body as { name: string };
  // Guardar en DB...
  res.writeHead(201, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ ...user, id: 1 }));
});

// Iniciar servidor
const baseUrl = await server.start();
console.log(`Server running at ${baseUrl}`);

// Hacer requests con TestClient
const client = new TestClient(baseUrl);
const response = await client.get('/api/users');

// Detener servidor
await server.stop();

TestServer con middleware

server.use(async (req, res) => {
  // Middleware de logging
  console.log(`${req.method} ${req.url}`);
  // Agregar header
  res.setHeader('X-Custom-Header', 'value');
});

server.get('/api/data', (req, res) => {
  // El middleware ya ejecutó
  res.writeHead(200);
  res.end('OK');
});

TestServer con params de ruta

server.get('/api/users/:id', (req, res) => {
  const id = req.params?.id; // Extraído automáticamente
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ id, name: 'User' }));
});

// GET /api/users/123 → req.params = { id: '123' }

Factories

import { createFactory, createSequence } from '@jamx-framework/testing';

// Factory de usuarios
const makeUser = createFactory({
  id: '1',
  name: 'Test User',
  email: '[email protected]',
  role: 'user',
});

const user1 = makeUser(); // { id: '1', name: 'Test User', ... }
const admin = makeUser({ role: 'admin', name: 'Admin' }); // overrides

// Secuencia de IDs
const nextId = createSequence((n) => String(n));
const id1 = nextId(); // '1'
const id2 = nextId(); // '2'

// Secuencia de emails
const nextEmail = createSequence((n) => `user${n}@test.com`);
const email1 = nextEmail(); // '[email protected]'

Mocks

mockMailer

import { mockMailer } from '@jamx-framework/testing';

const mailer = mockMailer();

// Enviar email
await mailer.send({
  to: '[email protected]',
  subject: 'Welcome',
  html: '<h1>Hello</h1>',
});

// Enviar con template
await mailer.sendTemplate('welcome', {
  to: '[email protected]',
  data: { name: 'Alice' },
});

// Verificar emails enviados
expect(mailer.sent).toHaveLength(2);
expect(mailer.sent[0].to).toBe('[email protected]');
expect(mailer.lastSent()?.subject).toBe('Welcome');

// Limpiar
mailer.clear();

mockStorage

import { mockStorage } from '@jamx-framework/testing';

const storage = mockStorage('http://localhost/files');

// Subir archivo
await storage.put('avatar.png', Buffer.from('image data'), {
  contentType: 'image/png',
});

// Descargar archivo
const data = await storage.get('avatar.png');
expect(data?.toString()).toBe('image data');

// Verificar existencia
expect(await storage.exists('avatar.png')).toBe(true);

// Listar archivos
const files = await storage.list();
expect(files).toHaveLength(1);

// URL pública (para tests)
const url = await storage.url('avatar.png');
expect(url).toBe('http://localhost/files/avatar.png');

// Limpiar
storage.clear();

mockCache

import { mockCache } from '@jamx-framework/testing';

const cache = mockCache('test:');

// Guardar
await cache.set('key1', { value: 123 }, 60); // TTL 60s

// Obtener
const data = await cache.get('key1');
expect(data?.value).toBe(123);

// Verificar existencia
expect(await cache.has('key1')).toBe(true);

// Eliminar
await cache.delete('key1');

// Limpiar todo
cache.clear();

mockQueue

import { mockQueue } from '@jamx-framework/testing';

// Mock sin handler (solo registro)
const queue = mockQueue<{ userId: number }>();

// Añadir jobs
await queue.add('send-email', { userId: 1 });
await queue.add('send-email', { userId: 2 });

// Ver jobs encolados
expect(queue.jobs).toHaveLength(2);
expect(queue.jobs[0].name).toBe('send-email');

// Con handler para procesar
const queueWithHandler = mockQueue<{ userId: number }>(async (job) => {
  console.log(`Processing job ${job.id} for user ${job.data.userId}`);
});

await queueWithHandler.add('notify', { userId: 1 });
await queueWithHandler.processAll(); // Procesa todos los jobs

// Limpiar
queue.clear();

Ejemplo completo de test de integración

import { createTestServer, TestClient, createFactory } from '@jamx-framework/testing';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';

const makeUser = createFactory({
  id: 1,
  name: 'Test User',
  email: '[email protected]',
});

describe('User API', () => {
  let server: ReturnType<typeof createTestServer>;
  let client: TestClient;

  beforeEach(async () => {
    server = createTestServer();

    // Setup routes
    server.get('/api/users', (req, res) => {
      const users = [
        makeUser({ id: 1, name: 'Alice' }),
        makeUser({ id: 2, name: 'Bob' }),
      ];
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify(users));
    });

    server.post('/api/users', async (req, res) => {
      const user = req.body as { name: string; email: string };
      const newUser = makeUser({
        id: Date.now(),
        name: user.name,
        email: user.email,
      });
      res.writeHead(201, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify(newUser));
    });

    const baseUrl = await server.start();
    client = new TestClient(baseUrl);
  });

  afterEach(async () => {
    await server.stop();
  });

  it('should list all users', async () => {
    const response = await client.get('/api/users');
    expect(response.status).toBe(200);

    const users = response.json<Array<{ id: number; name: string }>>();
    expect(users).toHaveLength(2);
    expect(users[0].name).toBe('Alice');
  });

  it('should create a new user', async () => {
    const response = await client.post('/api/users', {
      body: { name: 'Charlie', email: '[email protected]' },
    });
    expect(response.status).toBe(201);

    const user = response.json<{ id: number; name: string; email: string }>();
    expect(user.name).toBe('Charlie');
    expect(user.email).toBe('[email protected]');
  });
});

API Reference

TestClient

Constructor

new TestClient(baseUrl: string)

Crea un cliente HTTP que apunta a baseUrl.

Métodos

get<T = unknown>(path: string, options?: RequestOptions): Promise<TestResponse>
post<T = unknown>(path: string, options?: RequestOptions): Promise<TestResponse>
put<T = unknown>(path: string, options?: RequestOptions): Promise<TestResponse>
patch<T = unknown>(path: string, options?: RequestOptions): Promise<TestResponse>
delete<T = unknown>(path: string, options?: RequestOptions): Promise<TestResponse>

TestResponse

interface TestResponse {
  status: number;
  headers: Record<string, string>;
  body: string;
  json<T = unknown>(): T;
  text(): string;
}

RequestOptions

interface RequestOptions {
  headers?: Record<string, string>;
  body?: unknown; // Se serializa como JSON automáticamente
}

TestServer

Interface

interface TestServer {
  get(path: string, handler: TestHandler): this;
  post(path: string, handler: TestHandler): this;
  put(path: string, handler: TestHandler): this;
  delete(path: string, handler: TestHandler): this;
  use(handler: TestHandler): this;
  start(): Promise<string>; // retorna baseUrl
  stop(): Promise<void>;
  url: string;
}

TestHandler

type TestHandler = (
  req: http.IncomingMessage & {
    body?: unknown;
    params?: Record<string, string>;
  },
  res: http.ServerResponse,
) => void | Promise<void>;

Factories

createFactory

function createFactory<T extends object>(defaults: T): (overrides?: DeepPartial<T>) => T

Crea una función que retorna objetos con valores por defecto, fusionando overrides.

createSequence

function createSequence<T>(fn: (n: number) => T): () => T

Crea un generador que retorna valores secuenciales únicos.

Mocks

mockMailer

function mockMailer(): MockMailer

MockMailer

interface MockMailer {
  send(msg: MockMailMessage): Promise<void>;
  sendTemplate(template: string, options: { to: string; data?: Record<string, unknown> }): Promise<void>;
  sent: MockMailMessage[];
  clear(): void;
  hasSent(to: string): boolean;
  lastSent(): MockMailMessage | null;
}

mockStorage

function mockStorage(baseUrl?: string): MockStorage

MockStorage

interface MockStorage {
  put(key: string, data: Buffer, options?: { contentType?: string }): Promise<void>;
  get(key: string): Promise<Buffer | null>;
  delete(key: string): Promise<boolean>;
  exists(key: string): Promise<boolean>;
  url(key: string): Promise<string>;
  list(prefix?: string): Promise<MockStorageFile[]>;
  files: Map<string, MockStorageFile>;
  clear(): void;
}

mockCache

function mockCache(prefix?: string): Cache

Retorna una instancia de Cache con driver memory.

mockQueue

function mockQueue<T = unknown>(handler?: MockJobHandler<T>): MockQueue<T>

MockQueue

interface MockQueue<T = unknown> {
  add(name: string, data: T): Promise<MockJob<T>>;
  processAll(): Promise<void>;
  jobs: MockJob<T>[];
  clear(): void;
}

Configuración

tsconfig.json

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "dist",
    "tsBuildInfoFile": "dist/.tsbuildinfo"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "tests"]
}

Scripts disponibles

  • pnpm build - Compila TypeScript a JavaScript
  • pnpm dev - Compilación en watch mode
  • pnpm test - Ejecuta tests unitarios
  • pnpm test:watch - Tests en watch mode
  • pnpm type-check - Verifica tipos sin compilar
  • pnpm clean - Limpia archivos compilados

Testing

Tests en packages/testing/tests/unit/:

  • factories.test.ts: Tests de createFactory y createSequence
  • mocks.test.ts: Tests de todos los mocks
  • test-server.test.ts: Tests de TestServer

Ejecutar tests:

pnpm test

Dependencias

  • @jamx-framework/cache - Para mockCache
  • @jamx-framework/core - Para tipos base
  • @jamx-framework/server - Para TestServer
  • @jamx-framework/validator - Para validación (opcional)
  • @types/node - Tipos de Node.js
  • vitest - Framework de testing

Casos de uso

Tests de integración con API real

// Usar TestClient para probar un servidor JAMX real
const client = new TestClient('http://localhost:3000');

test('should create user', async () => {
  const res = await client.post('/users', {
    body: { name: 'Alice', email: '[email protected]' },
  });
  expect(res.status).toBe(201);
  const user = res.json<User>();
  expect(user.id).toBeDefined();
});

Tests de endpoints aislados

// Usar TestServer para probar lógica de endpoints sin levantar servidor completo
const server = createTestServer();
server.get('/health', (req, res) => {
  res.writeHead(200);
  res.end(JSON.stringify({ status: 'ok' }));
});

await server.start();
const client = new TestClient(server.url);
const res = await client.get('/health');
expect(res.json()).toEqual({ status: 'ok' });
await server.stop();

Tests con datos de ejemplo

const makeProduct = createFactory({
  id: 0,
  name: 'Product',
  price: 0,
  inStock: true,
});

test('should filter products', () => {
  const products = [
    makeProduct({ id: 1, name: 'Apple', price: 1.5 }),
    makeProduct({ id: 2, name: 'Banana', price: 0.8 }),
    makeProduct({ id: 3, name: 'Carrot', price: 0.5 }),
  ];

  const cheap = products.filter(p => p.price < 1);
  expect(cheap).toHaveLength(2);
});

Tests de servicios con mocks

import { mockMailer, mockStorage } from '@jamx-framework/testing';

test('should send welcome email on signup', async () => {
  const mailer = mockMailer();
  const user = { id: 1, email: '[email protected]', name: 'Alice' };

  // Lógica de signup que usa mailer
  await mailer.send({
    to: user.email,
    subject: 'Welcome!',
    html: `<h1>Hello ${user.name}</h1>`,
  });

  expect(mailer.sent).toHaveLength(1);
  expect(mailer.sent[0].to).toBe(user.email);
});

test('should upload file to storage', async () => {
  const storage = mockStorage();
  const fileData = Buffer.from('file content');

  await storage.put('documents/file.txt', fileData, {
    contentType: 'text/plain',
  });

  expect(await storage.exists('documents/file.txt')).toBe(true);
  const retrieved = await storage.get('documents/file.txt');
  expect(retrieved?.toString()).toBe('file content');
});

Limitaciones

  • TestServer: No soporta HTTPS (solo HTTP)
  • TestServer: No tiene soporte para WebSocket
  • TestClient: Bloqueante en Node.js (pero usa async/await)
  • Mocks: Todos en memoria, no persisten entre tests
  • mockQueue: Procesa jobs secuencialmente, no en paralelo

Buenas prácticas

  1. Siempre limpiar mocks:
afterEach(() => {
  mailer.clear();
  storage.clear();
  cache.clear();
  queue.clear();
});
  1. Usar factories para datos de test:
const makeUser = createFactory({ id: 1, name: 'User', email: '[email protected]' });
const user = makeUser({ name: 'Alice' }); // más legible
  1. Asegurar puertos libres:
// TestServer ya usa puerto 0 (aleatorio), no hay conflicto
  1. Mockear dependencias externas:
// En lugar de llamar a API real, usar mockStorage
const storage = mockStorage();
await storage.put('key', data);
  1. Tests asincrónicos:
// TestServer.start() y stop() son async
beforeEach(async () => {
  await server.start();
});

afterEach(async () => {
  await server.stop();
});

Integración con otros paquetes

  • @jamx-framework/server: TestServer puede simular endpoints de JAMX server
  • @jamx-framework/cache: mockCache retorna instancia de Cache real
  • @jamx-framework/storage: mockStorage implementa StorageAdapter
  • @jamx-framework/queue: mockQueue simula Queue
  • @jamx-framework/mailer: mockMailer simula Mailer

Ejemplo: Test completo de API JAMX

import { createTestServer } from '@jamx-framework/testing';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';

// Simular una API JAMX completa
const server = createTestServer();

server.use((req, res) => {
  // Middleware de logging
  console.log(`${req.method} ${req.url}`);
  next();
});

server.get('/api/health', (req, res) => {
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ status: 'ok', timestamp: Date.now() }));
});

server.post('/api/auth/login', async (req, res) => {
  const { email, password } = req.body as { email: string; password: string };
  if (email === '[email protected]' && password === 'pass') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ token: 'jwt-token', user: { id: 1, email } }));
  } else {
    res.writeHead(401);
    res.end(JSON.stringify({ error: 'Invalid credentials' }));
  }
});

describe('API Integration Tests', () => {
  let client: TestClient;

  beforeEach(async () => {
    await server.start();
    client = new TestClient(server.url);
  });

  afterEach(async () => {
    await server.stop();
  });

  it('health check', async () => {
    const res = await client.get('/api/health');
    expect(res.status).toBe(200);
    expect(res.json()).toHaveProperty('status', 'ok');
  });

  it('login success', async () => {
    const res = await client.post('/api/auth/login', {
      body: { email: '[email protected]', password: 'pass' },
    });
    expect(res.status).toBe(200);
    const data = res.json<{ token: string; user: { id: number; email: string } }>();
    expect(data.token).toBe('jwt-token');
  });

  it('login failure', async () => {
    const res = await client.post('/api/auth/login', {
      body: { email: '[email protected]', password: 'wrong' },
    });
    expect(res.status).toBe(401);
  });
});