【问题标题】:Require external modules as ES6 class instance variables需要外部模块作为 ES6 类实例变量
【发布时间】:2017-12-22 03:17:39
【问题描述】:

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

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

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

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

class MyClass {

    constructor() {
        this.someModule = someModule;
    }

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

}

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

这是否被认为是不好的做法,或者您能看出任何主要缺点吗?

【问题讨论】:

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


    【解决方案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);
    

    【讨论】:

    • 我会接受您的回答并尝试使用 DI 框架。非常感谢您提供的详细信息 :) 一些进一步的信息:我对节点可用的各种 DI 框架进行了更广泛的研究,并尝试了其中的几个。我发现github.com/justmoon/constitute 有一个非常干净和轻量级的 API(似乎也受到了 Angular DI 概念的启发)。
    • 不客气。谢谢分享,我去看看。作者承认它大部分复制了 Aurelia DI,因此您可以查看its manual 了解一些概念。 injection-js 的好处是它得到了支持,并且在 Angular 手册中有很好的记录,对于 Angular 全栈开发也非常简单。
    猜你喜欢
    • 2011-04-24
    • 2020-04-07
    • 1970-01-01
    • 1970-01-01
    • 2012-08-20
    • 1970-01-01
    • 2013-05-02
    • 1970-01-01
    • 2011-05-11
    相关资源
    最近更新 更多