需要外部模块作为ES6类实例变量

时间:2017-07-17 14:19:11

标签: javascript node.js unit-testing ecmascript-6 es6-class

我有一个ES6类,它依赖于一些外部模块来工作。由于这是一个节点应用程序,我使用CommonJS来请求和加载模块。

然而,这种模块加载使单元测试变得复杂并不是什么秘密。我当然可以通过构造函数注入所有必需的模块,但是这在动态类型语言中感觉很麻烦。我也不喜欢使用像proxyquire这样的库,因为它会增加我的测试代码。

所以我提出了将所需模块存储为实例变量的想法。例如:

const someModule = require('some-module');

class MyClass {

    constructor() {
        this.someModule = someModule;
    }

    someFunction(value) {
        return this.someModule.someFunction(value);
    }

}

这样我可以使用模块加载器加载依赖项,并在单元测试中仍然使用spy / stub / mock。

这被认为是不好的做法,还是会看到任何重大缺点?

1 个答案:

答案 0 :(得分:1)

这在个案基础上肯定是可以接受的。静态或原型someModule属性将更有效,但另一方面,这需要在测试中进行模拟后恢复它。

定期地,这种模式可能变得麻烦,在这种情况下DI容器可能更方便。在Node领域中有很多它们,例如injection-js that was extracted from Angular DI

在最简单的形式中,它可以是纯粹的单例容器,它不会自己创建实例,而是将现有值(模块导出)存储在随机令牌下:

class Container extends Map {
  get(key) {
    if (this.has(key)) {
      return super.get(key);
    } else {
      throw new Error('Unknown dependency token ' + String(key));
    }
  }

  set(key, val) {
    if (key == null) {
      throw new Error('Nully dependency token ' + String(key));
    } else if (arguments.length == 1) {
      super.set(key, key);
    } else {
      super.set(key, val);
    }
  }
}

const container = new Container;

可以直接从容器中注册和检索依赖项:

const foo = Symbol('foo');
container.set(foo, require('foo'));
container.set('bar', require('bar'));
container.set(require('baz'));
...
const { foo } = require('./common-deps');

class Qux {
  constructor() {
    this.foo = container.get(foo);
    ...
  }
}

此外,注射器可以包含容器:

class DI {
  constructor(container) {
    this.container = container;
  }

  new(Fn) {
    if (!Array.isArray(Fn.annotation)) {
      throw new Error(Fn + ' is not annotated');
    }

    return new Fn(...Fn.annotation.map(key => this.container.get(key)));
  }

  call(fn) {
    if (!Array.isArray(fn.annotation)) {
      throw new Error(fn + ' is not annotated');
    }

    return fn(...fn.annotation.map(key => this.container.get(key)));
  }
}

const di = new DI(container);

在注释的类和函数中处理DI(在注释上,请参阅this explanation):

class Qux {
  constructor(foo, bar) {
    this.foo = foo;
    ...
  }
}
Qux.annotation = [foo, 'bar', require('baz')];

quuxFactory.annotation = [require('baz')]
function quuxFactory(baz) { ... }

const qux = di.new(Qux);
const quux = di.call(quuxFactory);