Introducción: Por qué necesitas pruebas de integración con TypeScript y Docker
Las pruebas de integración son una etapa clave del desarrollo que verifica la interacción entre diferentes módulos de tu aplicación. A diferencia de las pruebas unitarias, que aíslan un solo componente, las pruebas de integración verifican cómo funcionan juntos la base de datos, la API, los servicios externos y la lógica de negocio.
El uso de Docker en las pruebas de integración con TypeScript resuelve varios problemas:
- Aislamiento del entorno — las pruebas se ejecutan en condiciones idénticas en todas las máquinas
- Gestión de dependencias — bases de datos, colas y cachés se inician automáticamente
- Reproducibilidad — las pruebas producen los mismos resultados localmente y en CI/CD
- Recuperación rápida — los contenedores se recrean en segundos
En este artículo, recorreremos un ejemplo práctico de configuración de pruebas de integración para una aplicación TypeScript usando Docker, Jest y Supertest.
1. Configuración del proyecto e instalación de dependencias
Comencemos creando un proyecto TypeScript básico. Asegúrate de tener Node.js (v18+) y Docker instalados.
mkdir typescript-docker-testingcd typescript-docker-testingnpm init -ynpm install express typescript ts-node @types/node @types/expressnpm install --save-dev jest ts-jest @types/jest supertest @types/supertestCrea tsconfig.json:
{ "compilerOptions": { "target": "ES2020", "module": "commonjs", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true }, "include": ["src/**/*"], "exclude": ["node_modules", "**/*.test.ts"]}Configura Jest en package.json:
"jest": { "preset": "ts-jest", "testEnvironment": "node", "testMatch": ["**/*.test.ts"]}2. Creación de una aplicación Express simple
Creemos la estructura de carpetas y una aplicación simple que interactuará con una base de datos PostgreSQL.
mkdir srcmkdir src/configmkdir src/modelsmkdir src/routesmkdir src/__tests__src/config/database.ts — configuración de la conexión a la base de datos:
import { Pool } from 'pg';
const pool = new Pool({ host: process.env.DB_HOST || 'localhost', port: Number(process.env.DB_PORT) || 5432, database: process.env.DB_NAME || 'testdb', user: process.env.DB_USER || 'testuser', password: process.env.DB_PASSWORD || 'testpass'});
export default pool;src/app.ts — aplicación principal:
import express from 'express';import userRoutes from './routes/users';
const app = express();app.use(express.json());app.use('/api/users', userRoutes);
export default app;src/index.ts — punto de entrada:
import app from './app';
const PORT = process.env.PORT || 3000;app.listen(PORT, () => { console.log(`Servidor ejecutándose en el puerto ${PORT}`);});src/routes/users.ts — ruta de usuario simple:
import { Router, Request, Response } from 'express';import pool from '../config/database';
const router = Router();
router.get('/', async (req: Request, res: Response) => { try { const result = await pool.query('SELECT * FROM users'); res.json(result.rows); } catch (error) { res.status(500).json({ error: 'Error de base de datos' }); }});
router.post('/', async (req: Request, res: Response) => { const { name, email } = req.body; try { const result = await pool.query( 'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *', [name, email] ); res.status(201).json(result.rows[0]); } catch (error) { res.status(500).json({ error: 'Error de base de datos' }); }});
export default router;