nock para TypeScript: simulación HTTP en pruebas
1. ¿Qué es nock y para qué sirve?
nock es una potente biblioteca para Node.js diseñada para interceptar y simular (mockear) peticiones HTTP. Permite a los desarrolladores imitar respuestas de servicios externos (APIs REST, microservicios de terceros, webhooks) sin necesidad de realizar llamadas de red reales. Esto es especialmente importante al escribir pruebas unitarias y de integración, donde la estabilidad y la velocidad de ejecución son críticas.
¿Por qué es útil? Imagina que tu aplicación envía peticiones a una pasarela de pago, una API del clima o una base de datos a través de HTTP. En un entorno de pruebas no quieres depender de la disponibilidad de esos servicios, su velocidad de respuesta o sus límites de peticiones. nock te permite "suplantar" esas peticiones, devolviendo datos predefinidos. Esto hace que las pruebas sean rápidas, predecibles y aisladas de factores externos. Además, puedes probar escenarios de error (timeouts, códigos 500, respuestas vacías) que son difíciles de reproducir en condiciones reales.
En el contexto de TypeScript, nock se integra perfectamente con frameworks de pruebas populares (Jest, Mocha, Vitest) y ofrece soporte de tipado, lo que hace que trabajar con ella sea cómodo y seguro. La biblioteca intercepta peticiones a nivel de Node.js (a través de los módulos http y https), por lo que funciona con cualquier cliente HTTP: axios, node-fetch, got e incluso el fetch nativo (con algunas salvedades).
2. Instalación
Para instalar nock en un proyecto TypeScript, ejecuta uno de los siguientes comandos en tu terminal:
# Con npm
npm install --save-dev nock @types/nock
# Con yarn
yarn add --dev nock @types/nock
# Con pnpm
pnpm add --save-dev nock @types/nock
Importante: @types/nock contiene las declaraciones de tipos para TypeScript. Si usas TypeScript 4.0+, los tipos podrían venir incluidos en la propia biblioteca, pero para mayor compatibilidad es mejor instalarlos explícitamente.
3. Inicio rápido
Escribamos una prueba mínima que demuestre el funcionamiento básico de nock. Supongamos que tenemos una función que obtiene datos de un usuario desde una API externa.
// userService.ts
import axios from 'axios';
export async function getUser(userId: number): Promise<{ id: number; name: string; email: string }> {
const response = await axios.get(`https://jsonplaceholder.typicode.com/users/${userId}`);
return response.data;
}
Ahora escribamos una prueba que simule esta petición:
// userService.test.ts
import nock from 'nock';
import { getUser } from './userService';
describe('getUser', () => {
afterEach(() => {
nock.cleanAll(); // Limpiamos todos los mocks después de cada prueba
});
it('debe devolver los datos del usuario', async () => {
// 1. Configuramos la intercepción
const mockUser = { id: 1, name: 'Juan Pérez', email: 'juan@ejemplo.com' };
nock('https://jsonplaceholder.typicode.com')
.get('/users/1')
.reply(200, mockUser);
// 2. Llamamos a la función bajo prueba
const user = await getUser(1);
// 3. Verificamos el resultado
expect(user).toEqual(mockUser);
});
it('debe lanzar un error con código 500', async () => {
nock('https://jsonplaceholder.typicode.com')
.get('/users/1')
.reply(500, { error: 'Error del servidor' });
await expect(getUser(1)).rejects.toThrow();
});
});
Este ejemplo muestra lo fácil que es simular la respuesta de un servidor y probar tanto escenarios exitosos como de error.
4. Conceptos fundamentales
Para dominar nock, es importante entender sus conceptos clave. A continuación, los explicamos con ejemplos prácticos.
4.1. Interceptor
Un interceptor es la unidad básica de nock. Se crea llamando a nock(baseUrl) y encadenando métodos para definir la petición a interceptar.
const interceptor = nock('https://api.ejemplo.com')
.get('/ruta')
.reply(200, { mensaje: 'Hola' });
El interceptor permanece activo hasta que se usa una vez, a menos que se configure con .persist().
4.2. Métodos HTTP soportados
nock soporta todos los métodos HTTP comunes: .get(), .post(), .put(), .patch(), .delete(), .head() y .options().
nock('https://api.ejemplo.com')
.post('/usuarios', { nombre: 'Ana' })
.reply(201, { id: 123 });
4.3. Filtrado de rutas y cabeceras
Puedes usar funciones para filtrar rutas o cabeceras de forma dinámica:
nock('https://api.ejemplo.com')
.get('/usuarios')
.query({ rol: 'admin' })
.reply(200, [{ id: 1, nombre: 'Admin' }]);
// También puedes filtrar por cabeceras
nock('https://api.ejemplo.com', {
reqheaders: {
'Authorization': 'Bearer token123'
}
})
.get('/perfil')
.reply(200, { email: 'admin@ejemplo.com' });
4.4. Respuestas dinámicas
Puedes devolver respuestas basadas en el cuerpo de la petición usando una función:
nock('https://api.ejemplo.com')
.post('/calcular')
.reply(200, (uri, requestBody) => {
const datos = JSON.parse(requestBody);
return { resultado: datos.a + datos.b };
});
5. Uso avanzado
Más allá de lo básico, nock ofrece funcionalidades avanzadas para escenarios complejos.
5.1. Persistencia de interceptores
Por defecto, un interceptor se elimina después de ser usado una vez. Para que persista, usa .persist():
nock('https://api.ejemplo.com')
.get('/recurso')
.times(3) // Se usará exactamente 3 veces
.reply(200, { datos: 'persistente' });
// O para que se use indefinidamente:
nock('https://api.ejemplo.com')
.get('/recurso')
.persist()
.reply(200, { datos: 'siempre disponible' });
5.2. Simulación de retrasos y errores de red
Puedes simular latencia o errores de conexión:
// Simular un retraso de 2 segundos
nock('https://api.ejemplo.com')
.get('/lento')
.delay(2000)
.reply(200, { mensaje: 'respuesta tardía' });
// Simular un error de conexión
nock('https://api.ejemplo.com')
.get('/error-red')
.replyWithError('Error de conexión simulada');
5.3. Registro de peticiones (logging)
Para depurar, puedes activar el registro de peticiones interceptadas:
nock.recorder.rec({
dont_print: true,
output_objects: true
});
// Después de hacer las peticiones, obtén el registro
const registros = nock.recorder.play();
5.4. Limpieza y reinicio
Es importante limpiar los interceptores entre pruebas para evitar efectos secundarios:
afterEach(() => {
nock.cleanAll(); // Limpia todos los interceptores
nock.restore(); // Restaura el comportamiento HTTP original (si se usó nock.activate())
});
6. Integración con TypeScript
TypeScript ofrece ventajas adicionales al usar nock, como el tipado estricto y la detección temprana de errores.
6.1. Tipado de respuestas
Puedes definir interfaces para tus respuestas simuladas y asegurarte de que coinciden con lo que espera tu aplicación:
interface Usuario {
id: number;
nombre: string;
email: string;
}
const usuarioMock: Usuario = {
id: 1,
nombre: 'María García',
email: 'maria@ejemplo.com'
};
nock('https://api.ejemplo.com')
.get('/usuarios/1')
.reply(200, usuarioMock);
6.2. Uso con async/await y promesas
nock funciona perfectamente con código asíncrono en TypeScript:
async function obtenerUsuario(id: number): Promise {
const respuesta = await fetch(`https://api.ejemplo.com/usuarios/${id}`);
return respuesta.json();
}
// Prueba
it('debe obtener el usuario correctamente', async () => {
nock('https://api.ejemplo.com')
.get('/usuarios/1')
.reply(200, { id: 1, nombre: 'Carlos', email: 'carlos@ejemplo.com' });
const usuario = await obtenerUsuario(1);
expect(usuario.nombre).toBe('Carlos');
});
6.3. Configuración con Jest y Vitest
Para proyectos con Jest o Vitest, puedes configurar nock en un archivo de setup:
// setupTests.ts
import nock from 'nock';
beforeAll(() => {
nock.disableNetConnect(); // Bloquea todas las conexiones reales
});
afterAll(() => {
nock.enableNetConnect(); // Restaura las conexiones
nock.cleanAll();
});
7. Ejemplos prácticos
Veamos algunos casos de uso comunes en proyectos reales.
7.1. Simular una API REST completa
// Simular múltiples endpoints de una API
const api = nock('https://api.ejemplo.com');
api.get('/productos')
.reply(200, [
{ id: 1, nombre: 'Laptop', precio: 1200 },
{ id: 2, nombre: 'Mouse', precio: 25 }
]);
api.post('/productos', { nombre: 'Teclado', precio: 80 })
.reply(201, { id: 3, nombre: 'Teclado', precio: 80 });
api.delete('/productos/1')
.reply(204);
7.2. Probar autenticación OAuth
nock('https://auth.ejemplo.com')
.post('/oauth/token', {
grant_type: 'client_credentials',
client_id: 'mi-cliente',
client_secret: 'secreto'
})
.reply(200, {
access_token: 'token-simulado',
token_type: 'Bearer',
expires_in: 3600
});
7.3. Simular webhooks
// Simular la recepción de un webhook
nock('https://webhook.ejemplo.com')
.post('/eventos', {
tipo: 'pago_recibido',
datos: { monto: 100, moneda: 'USD' }
})
.reply(200, { estado: 'procesado' });
8. Buenas prácticas y consejos
- Siempre limpia los interceptores: Usa
nock.cleanAll()enafterEachpara evitar que las pruebas se afecten entre sí. - Bloquea conexiones reales: Usa
nock.disableNetConnect()en tu configuración de pruebas para asegurarte de que no se hagan llamadas reales accidentalmente. - Usa
.times()para controlar la cantidad de usos: Esto ayuda a verificar que tu código hace el número esperado de peticiones. - Mantén los mocks cerca de las pruebas: Define los interceptores dentro de cada prueba o en funciones helper específicas para mantener la claridad.
- Prueba también escenarios negativos: No solo simules respuestas exitosas; prueba errores, timeouts y respuestas vacías.
- Documenta los mocks complejos: Si un mock es muy elaborado, añade comentarios explicando qué está simulando.
9. Conclusión
nock es una herramienta indispensable para cualquier desarrollador que trabaje con TypeScript y necesite realizar pruebas confiables y rápidas. Su capacidad para interceptar peticiones HTTP a nivel de Node.js, combinada con el tipado de TypeScript, permite crear suites de pruebas robustas y mantenibles.
Hemos cubierto desde la instalación básica hasta técnicas avanzadas como la simulación de retrasos, el filtrado de cabeceras y la integración con frameworks de pruebas modernos. Con estos conocimientos, podrás escribir pruebas que no dependan de servicios externos, sean predecibles y cubran tanto casos de éxito como de error.
¿Listo para empezar? Instala nock en tu proyecto y comienza a escribir pruebas más sólidas hoy mismo. Recuerda: unas buenas pruebas son la base de un software de calidad.
También en la biblioteca
Dapper para C# (.NET): guía completa con ejemplos
Detekt para Kotlin: guía completa con ejemplos prácticos
yaml-cpp para C++: guía completa con ejemplos prácticos
CLI framework para Rust: Clap — una herramienta potente y conveniente para crear aplicaciones de consola