Injeção de Dependência em JavaScript: guia prático para Node.js

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: O problema do acoplamento rígido de código



Todo desenvolvedor Node.js, mais cedo ou mais tarde, enfrenta uma situação onde uma alteração em um módulo desencadeia uma cadeia de correções em dezenas de outros. Isso se torna especialmente crítico ao escrever testes unitários: para testar um serviço, é necessário levantar banco de dados, APIs externas e cache. A raiz do problema é o acoplamento rígido (tight coupling).



Injeção de Dependência (DI) é um padrão de projeto que resolve esse problema. Em vez de uma classe ou função criar suas próprias dependências, elas as recebem externamente. Isso torna o código flexível, testável e alinhado ao princípio da inversão de dependência (Dependency Inversion Principle) do SOLID.



Neste artigo, abordaremos DI na prática: desde a injeção manual simples até o uso de contêineres profissionais em Node.js.



1. Injeção manual de dependências: o primeiro passo



Abordagem ruim: acoplamento rígido

Vamos considerar um exemplo típico de serviço para envio de email:



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

class UserService { async registerUser(email) { // Dependência rígida de uma implementação específica await emailService.sendWelcomeEmail(email); console.log('Usuário registrado'); }}

module.exports = UserService;


Problema: ao testar UserService, não é possível substituir emailService por um mock. Seria necessário usar ferramentas complexas como proxyquire ou jest.mock.



Abordagem boa: injeção via construtor

Vamos reescrever o serviço, passando a dependência pelo construtor:



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

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

module.exports = UserService;


Agora podemos testar o serviço facilmente:



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

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

test('deve enviar email de boas-vindas', async () => { const service = new UserService(mockEmailService); await service.registerUser('test@test.com'); expect(mockEmailService.sendWelcomeEmail).toHaveBeenCalledWith('test@test.com');});


2. Tipos de Injeção de Dependência em JavaScript



Em JavaScript/Node.js, existem três formas principais de injetar dependências:



2.1. Injeção via construtor

A forma mais comum e previsível. As dependências são passadas na criação do objeto:



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

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

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


2.2. Injeção via setters

Adequada quando as dependências podem ser alteradas durante a execução do 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('Aplicação iniciada');


2.3. Injeção via parâmetros de função

Ideal para abordagem funcional e utilitários:



function sendNotification(user, emailService, smsService) {  emailService.send(user.email, 'Notificação');  smsService.send(user.phone, 'Notificação');}

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


3.

Blogs

Book Recommendations