需要外部模块作为 ES6 class 实例变量
Require external modules as ES6 class instance variables
我有一个 ES6 class,它依赖于一些外部模块来工作。由于这是一个节点应用程序,我使用 CommonJS 来要求和加载模块。
然而,这种模块加载使单元测试变得复杂已经不是什么秘密了。我当然可以通过构造函数依赖注入所有必需的模块,但是这在动态类型语言中感觉很麻烦。我也不喜欢使用像 proxyquire 这样的库,因为它会使我的测试代码膨胀。
所以我想出了将所需模块存储为实例变量的想法。例如:
const someModule = require('some-module');
class MyClass {
constructor() {
this.someModule = someModule;
}
someFunction(value) {
return this.someModule.someFunction(value);
}
}
这样我可以使用模块加载器加载依赖项,并且仍然 spy/stub/mock 它们在我的单元测试中。
这被认为是不好的做法还是您能看出任何主要缺点?
根据具体情况,这肯定是可以接受的。静态或原型 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(关于注释,参见 ):
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);
我有一个 ES6 class,它依赖于一些外部模块来工作。由于这是一个节点应用程序,我使用 CommonJS 来要求和加载模块。
然而,这种模块加载使单元测试变得复杂已经不是什么秘密了。我当然可以通过构造函数依赖注入所有必需的模块,但是这在动态类型语言中感觉很麻烦。我也不喜欢使用像 proxyquire 这样的库,因为它会使我的测试代码膨胀。
所以我想出了将所需模块存储为实例变量的想法。例如:
const someModule = require('some-module');
class MyClass {
constructor() {
this.someModule = someModule;
}
someFunction(value) {
return this.someModule.someFunction(value);
}
}
这样我可以使用模块加载器加载依赖项,并且仍然 spy/stub/mock 它们在我的单元测试中。
这被认为是不好的做法还是您能看出任何主要缺点?
根据具体情况,这肯定是可以接受的。静态或原型 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(关于注释,参见
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);