Inyección de Dependencias en JavaScript: guía práctica para Node.js

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: El problema del acoplamiento rígido del código



Cada desarrollador de Node.js, tarde o temprano, se enfrenta a la situación en la que un cambio en un módulo arrastra una cadena de modificaciones en una docena de otros. Esto se manifiesta especialmente al escribir pruebas unitarias: para probar un servicio, hay que levantar la base de datos, APIs externas y la caché. La raíz del problema es el acoplamiento rígido (tight coupling).



La Inyección de Dependencias (DI) es un patrón de diseño que resuelve este problema. En lugar de que una clase o función cree sus propias dependencias, las recibe desde el exterior. Esto hace que el código sea flexible, testeable y que cumpla con el principio de inversión de dependencias (Dependency Inversion Principle) de SOLID.



En este artículo analizaremos la DI en la práctica: desde la inyección manual simple hasta el uso de contenedores profesionales en Node.js.



1. Inyección manual de dependencias: el primer paso



Enfoque incorrecto: acoplamiento rígido

Consideremos un ejemplo típico de un servicio para enviar correos electrónicos:



// userService.jsconst emailService = require('./emailService');

class UserService { async registerUser(email) { // Dependencia rígida de una implementación concreta await emailService.sendWelcomeEmail(email); console.log('Usuario registrado'); }}

module.exports = UserService;


Problema: al probar UserService es imposible sustituir emailService por un mock. Habrá que usar herramientas complejas como proxyquire o jest.mock.



Enfoque correcto: inyección a través del constructor

Reescribamos el servicio, pasando la dependencia a través del constructor:



// userService.jsclass UserService {  constructor(emailService) {    this.emailService = emailService;  }

async registerUser(email) { await this.emailService.sendWelcomeEmail(email); console.log('Usuario registrado'); }}

module.exports = UserService;


Ahora podemos probar el servicio fácilmente:



// userService.test.jsconst UserService = require('./userService');

const mockEmailService = { sendWelcomeEmail: jest.fn().mockResolvedValue(true)};

test('debe enviar un correo de bienvenida', async () => { const service = new UserService(mockEmailService); await service.registerUser('test@test.com'); expect(mockEmailService.sendWelcomeEmail).toHaveBeenCalledWith('test@test.com');});


2. Tipos de Inyección de Dependencias en JavaScript



En JavaScript/Node.js existen tres formas principales de inyectar dependencias:



2.1. Inyección a través del constructor

La forma más común y predecible. Las dependencias se pasan al crear el objeto:



class DatabaseService {  constructor(connection) {    this.connection = connection;  }

async query(sql) { return this.connection.execute(sql); }}

// Usoconst db = new DatabaseService(new MySQLConnection());


2.2. Inyección a través de setters

Adecuado cuando las dependencias pueden cambiarse durante la vida del objeto:



class LoggerService {  setLogger(logger) {    this.logger = logger;  }

log(message) { this.logger.write(`[${new Date().toISOString()}] ${message}`); }}

const logger = new LoggerService();logger.setLogger(new ConsoleLogger());logger.log('Aplicación iniciada');


2.3. Inyección a través de parámetros de función

Ideal para el enfoque funcional y utilidades:



function sendNotification(user, emailService, smsService) {  emailService.send(user.email, 'Notificación');  smsService.send(user.phone, 'Notificación');}

// Pruebaconst mockEmail = { send: jest.fn() };const mockSms = { send: jest.fn() };sendNotification({ email: 'a@b.com', phone: '123' }, mockEmail, mockSms);


3.

Blogs

Book Recommendations