Front End

26 fev, 2013

Injeção de dependência com Node.js

Publicidade

Falei recentemente sobre a injeção de dependência para ajudar você a entender uma forma simples de separar um pouco o seu código e ajudar a testá-lo. Às vezes, porém, em Node.js, um módulo vai depender de uma API de sistema fornecida pelo Node, o que pode tornar muito difícil de certificar que a dependência privada está sendo usada corretamente. A injeção de dependência normal não funciona nessa situação, mas não perca a esperança ainda.

require Causes Issues

O Node.js tornou muito fácil a importação das dependências via require. Ele funciona muito bem e é mais simples do que os carregadores do módulo AMD, como RequireJS. O problema entra em cena quando queremos simular essas dependências. Se o carregamento do módulo for controlado através de Node.js, como é que vamos assumir essa responsabilidade para permitir que objetos de simulação sejam usados durante o teste? Nós podemos usar o módulo vm do Node e carregar os módulos em um novo contexto, através vm.runInNewContext, onde podemos controlar a forma como require devolve módulos.

A solução

Graças a este artigo, uma solução muito decente e completa pode ser apresentada a você agora. Se você gostar dela, então, por favor, agradeça dê créditos ao Vojta Jina em How to Node. Abaixo está o código:

var vm = require('vm');
var fs = require('fs');
var path = require('path');

/**
* Helper for unit testing:
* – load module with mocked dependencies
* – allow accessing private state of the module
*
* @param {string} filePath Absolute path to module (file to load)
* @param {Object=} mocks Hash of mocked dependencies
*/
exports.loadModule = function(filePath, mocks) {
mocks = mocks || {};

// this is necessary to allow relative path modules within loaded file
// i.e. requiring ./some inside file /a/b.js needs to be resolved to /a/some
var resolveModule = function(module) {
if (module.charAt(0) !== '.') return module;
return path.resolve(path.dirname(filePath), module);
};

var exports = {};
var context = {
require: function(name) {
return mocks[name] || require(resolveModule(name));
},
console: console,
exports: exports,
module: {
exports: exports
}
};

vm.runInNewContext(fs.readFileSync(filePath), context);
return context;
};

Você também pode baixar o trecho de código diretamente aqui. Embora possa não ser a maior parte do código já postado em um artigo, ele poderia ter alguma explanação. Quando estamos testando, carregamos esse módulo no teste e depois usamos a função loadModule – em vez de require – para carregar o módulo que vamos testar.

O primeiro argumento, filePath, especifica onde vamos encontrar o módulo que vamos testar. O segundo argumento, mocks, contém um objeto cujos nomes da propriedade irão corresponder aos nomes dos módulos que o módulo que estamos testando vai tentar usar require. O valor atribuído a essas propriedades são os objetos de simulação que você está usando para substituir os módulos que normalmente usariam require.

Basicamente, tudo o que ele faz é utilizar vm para carregar e executar o módulo usando um “contexto” diferente. Em outras palavras, nós recriamos o que os globais são (como require e exports) para que possamos controlá-los. A primeira coisa a se observar aqui é a nova função require que disponibilizamos. Tudo que ela faz é verificar se temos uma dependência de simulação para o nome especificado e, se não tivermos, nós apenas delegamos a função require normal.

Exemplo utilizando o carregador de módulo

Se você ainda está um pouco confuso, você pode dar uma olhada no exemplo de código abaixo e vê-lo utilizado em contexto pode ajudá-lo a entender as coisas um pouco melhor. Primeiro, vamos criar um módulo simples.

var fs = require('fs');

module.exports = {
// Do something with `fs`
}

Apenas imagine que é algo legal, ok? Enfim, agora queremos testar esse módulo, mas queremos fazer uma simulação fs para ver como ele está sendo usado internamente.

// Jasmine's syntax http://pivotal.github.com/jasmine/
describe('someModule', function() {
var loadModule = require('module-loader').loadModule;
var module, fsMock;

beforeEach(function() {
fsMock = {
// a mock for `fs`
};

// load the module with mock fs instead of real fs
module = loadModule('./web-server.js', {fs: fsMock});
});

it('should work', function() {
// a test that utilizes the fact that we can now control `fs`
});
});

A principal coisa para se prestar atenção aqui são nas linhas 7 a 12, onde criamos um mock de objetos para fs e usamos a nossa nova função loadModule para prendê-lo no que o objeto que está sendo utilizado em nosso módulo sem valor acima (quero dizer, incrível! Lembre-se, é incrível, né?).

Conclusão

Na minha cabeça, isso só fortalece a grandeza de Node.js. Ele permite que você altere o contexto no qual é executado! Essa é uma maneira muito interessante de emular injeção de dependência, e eu tenho certeza de que ele pode ser útil para muito mais coisas. De qualquer forma, continue testando e usando as boas práticas.

***

Texto original disponível em http://www.joezimjs.com/javascript/dependency-injection-with-node-js/