关于你的问题:我认为 JS 社区没有通用的做法。我在野外见过这两种类型,需要修改(比如rewire or 代理查询)和构造函数注入(通常使用专用的 DI 容器)。不过,我个人认为不使用 DI 容器更适合 JS。那是因为 JS 是一种动态语言发挥一等公民的作用。让我解释一下:
使用 DI 容器强制构造函数注入对于一切。它会产生巨大的配置开销,主要原因有两个:
- 在单元测试中提供模拟
- 创建对其环境一无所知的抽象组件
关于第一个论点:我不会仅仅为了单元测试而调整我的代码。如果它使你的代码更干净、更简单、更通用且不易出错,那么就放弃吧。但如果你唯一的原因是你的单元测试,我不会采取权衡。您可以通过需要修改和猴子修补。如果您发现自己编写了太多模拟,那么您可能根本不应该编写单元测试,而应该编写集成测试。埃里克·埃利奥特写过一篇很棒的文章关于这个问题。
关于第二个论点: 这是一个有效的论点。如果您想创建一个只关心接口而不关心实际实现的组件,我会选择简单的构造函数注入。然而,既然 JS 并不强迫你在所有事情上都使用类,为什么不直接使用函数呢?
In 函数式编程,将有状态 IO 与实际处理分开是一种常见的范例。例如,如果您正在编写应该计算文件夹中文件类型的代码,则可以这样写(特别是当他/她来自一种到处强制执行类的语言时):
const fs = require("fs");
class FileTypeCounter {
countFileTypes(dirname, callback) {
fs.readdir(dirname, function (err) {
if (err) return callback(err);
// recursively walk all folders and count file types
// ...
callback(null, fileTypes);
});
}
}
现在,如果您想测试它,您需要更改代码以注入假的fs
module:
class FileTypeCounter {
constructor(fs) {
this.fs = fs;
}
countFileTypes(dirname, callback) {
this.fs.readdir(dirname, function (err) {
// ...
});
}
}
现在,每个使用你的类的人都需要注入fs
进入构造函数。由于这很无聊,并且一旦您拥有较长的依赖关系图,就会使您的代码变得更加复杂,因此开发人员发明了 DI 容器,他们可以在其中配置内容,然后 DI 容器会计算出实例化。
但是,只编写纯函数怎么样?
function fileTypeCounter(allFiles) {
// count file types
return fileTypes;
}
function getAllFilesInDir(dirname, callback) {
// recursively walk all folders and collect all files
// ...
callback(null, allFiles);
}
// now let's compose both functions
function getAllFileTypesInDir(dirname, callback) {
getAllFilesInDir(dirname, (err, allFiles) => {
callback(err, !err && fileTypeCounter(allFiles));
});
}
现在您拥有两个开箱即用的超级多功能函数,一个用于执行 IO,另一个用于处理数据。fileTypeCounter
is a 纯函数并且超级容易测试。getAllFilesInDir
是不纯粹的,但这是一个很常见的任务,你经常会发现它已经在npm其他人已经为其编写了集成测试。getAllFileTypesInDir
只需用一点控制流来组合您的函数即可。这是集成测试的典型案例,您希望确保整个应用程序正常工作。
通过将 IO 和数据处理之间的代码分开,您将根本不需要注入任何内容。如果您不需要注射任何东西,这是一个好兆头。纯函数是最容易测试的东西,并且仍然是在项目之间共享代码的最简单方法。