Introducción: Por qué las pruebas avanzadas son un must-have para un proyecto JavaScript
En el desarrollo moderno con Node.js, las pruebas han dejado de ser una etapa opcional. Sin una cobertura de código de calidad con pruebas, cualquier refactorización o adición de nueva funcionalidad corre el riesgo de convertirse en una "ruleta rusa". Sin embargo, simplemente escribir algunos assert no es suficiente. A nivel avanzado, el desarrollador debe dominar tres capas principales: Pruebas Unitarias (verificación de módulos aislados), Pruebas de Integración (interacción de componentes, BD, API) y Pruebas E2E (simulación de acciones del usuario). En este artículo, analizaremos técnicas prácticas para cada capa usando Node.js con herramientas modernas: Jest, Supertest y Playwright.
1. Pruebas Unitarias: aislamiento, mocks y stubs a nivel avanzado
Las pruebas unitarias verifican el funcionamiento de una función o módulo de forma aislada de las dependencias externas (BD, sistema de archivos, solicitudes de red). La tarea clave es simular correctamente estas dependencias para que la prueba sea rápida y determinista.
Técnicas avanzadas de mocking en Jest
Consideremos un ejemplo: una función que accede a una API externa y almacena en caché el resultado en Redis. Necesitamos probar la lógica de caché sin realizar solicitudes reales.
// cacheService.jsconst axios = require('axios');const redis = require('redis');
class CacheService { constructor() { this.client = redis.createClient(); }
async fetchData(url) { const cached = await this.client.get(url); if (cached) return JSON.parse(cached);
const response = await axios.get(url); await this.client.setEx(url, 3600, JSON.stringify(response.data)); return response.data; }}
module.exports = CacheService;// cacheService.test.jsconst CacheService = require('./cacheService');const axios = require('axios');const redis = require('redis');
jest.mock('axios');jest.mock('redis', () => ({ createClient: jest.fn(() => ({ get: jest.fn(), setEx: jest.fn() }))}));
describe('CacheService', () => { let service; let mockRedisClient;
beforeEach(() => { mockRedisClient = redis.createClient(); service = new CacheService(); });
test('should return cached data if present', async () => { const url = 'https://api.example.com/data'; const cachedData = { id: 1, value: 'cached' }; mockRedisClient.get.mockResolvedValue(JSON.stringify(cachedData));
const result = await service.fetchData(url);
expect(result).toEqual(cachedData); expect(axios.get).not.toHaveBeenCalled(); });
test('should fetch and cache data if not cached', async () => { const url = 'https://api.example.com/data'; const apiData = { id: 2, value: 'fresh' }; mockRedisClient.get.mockResolvedValue(null); axios.get.mockResolvedValue({ data: apiData });
const result = await service.fetchData(url);
expect(result).toEqual(apiData); expect(mockRedisClient.setEx).toHaveBeenCalledWith(url, 3600, JSON.stringify(apiData)); });});Puntos clave:
jest.mock('axios')simula automáticamente todo el módulo axios.- Para Redis usamos una fábrica que devuelve un mock con los métodos
getysetEx. - Verificamos que cuando hay caché, la llamada externa no ocurre.
2. Pruebas de Integración: probando la interacción con una BD real y API
Las pruebas de integración verifican cómo diferentes partes del sistema funcionan juntas. Para Node.js, esto a menudo implica probar rutas de Express con una base de datos real (o de prueba). La mejor herramienta es Supertest en combinación con Jest.
Configuración de una BD de prueba y Supertest
Ejemplo: API para gestionar usuarios. Usamos SQLite en memoria para aislamiento