32

我想存根 node.js 内置插件,fs这样我实际上不会进行任何系统级文件调用。我唯一能想到的就是将fs所有其他内置函数作为参数传递给我的所有函数,以避免使用真正的 fs。这似乎有点傻,并创建了一个冗长的函数签名,其中包含作为参数的内置函数。

var fs = require('fs');

function findFile(path, callback) {
  _findFile(fs, path, callback);
}

function _findFile(fs, path, callback) {
  fs.readdir(path, function(err, files) {
     //Do something.
  });
}

然后在测试期间:

var stubFs = {
  readdir: function(path, callback) {
     callback(null, []);
  }
};

_findFile.(stubFs, testThing, testCallback);

还有比这更好的方法吗?

4

11 回答 11

23

我喜欢使用rewire来删除 require(...) 语句

被测模块

模块-a.js

var fs = require('fs')
function findFile(path, callback) {
  fs.readdir(path, function(err, files) {
     //Do something.
  })
}

测试代码

模块-a-test.js

var rewire = require('rewire')
var moduleA = rewire('./moduleA')
// stub out fs
var fsStub = {
  readdir: function(path, callback) {
     console.log('fs.readdir stub called')
     callback(null, [])
  }
}
moduleA.__set__('fs', fsStub)
// call moduleA which now has a fs stubbed out
moduleA()
于 2013-03-30T23:26:35.990 回答
14

如果被测模块是对fs自身进行调用的模块,则重新布线和其他存根解决方案是很好的。但是,如果被测模块使用了在底层使用的库fs,那么重新布线和其他存根解决方案很快就会变得棘手。

现在有一个更好的解决方案:mock-fs

mock-fs 模块允许 Node 的内置fs模块由内存中的模拟文件系统临时支持。这使您可以针对一组模拟文件和目录运行测试,而不是拖着一堆测试装置。

示例(无耻地从自述文件中删除):

var mock = require('mock-fs');

mock({
  'path/to/fake/dir': {
    'some-file.txt': 'file content here',
    'empty-dir': {/** empty directory */}
  },
  'path/to/some.png': new Buffer([8, 6, 7, 5, 3, 0, 9]),
  'some/other/path': {/** another empty directory */}
});
于 2016-10-28T13:59:18.323 回答
13

存根是模拟组件/模块行为的函数/程序。存根为测试用例期间进行的函数调用提供预设答案。

一个例子可以是写一个文件,但实际上并没有这样做。

var fs = require('fs')

var writeFileStub = sinon.stub(fs, 'writeFile', function (path, data, cb) {  
 return cb(null)
})

expect(writeFileStub).to.be.called  
writeFileStub.restore()  
于 2017-09-04T11:56:13.750 回答
11

另一种选择(尽管我认为诺亚建议重新布线更好):

写一个包装器require,命名requireStubbable左右。将它放在您配置一次的模块中,在测试设置代码中。因为 node 缓存了 require 的结果,所以当你再次需要 requireStubbable 模块时,你会得到相同的配置函数。您可以对其进行配置,以便对任意数量的模块进行存根,而所有其他模块都将被原封不动地传递。

您想要支持传入存根的任何模块都需要使用该requireStubbable函数而不是常规函数require。rewire 模块没有这个缺点,而是将控制权交给了调用代码。

添加于 4 月 26 日

我从来没有意识到,但由于返回的对象(或更准确地说:对象引用)require("fs")被缓存,你可以简单地做:

const fs = require("fs")
fs.readFile = function (filename, cb) {
  cb(null, new Buffer("fake contents"));
};
// etc

当您在任何地方包含此代码时,fs.readFile将到处指向上述函数。这适用于存根只是功能集合的任何模块(如大多数内置模块)。如果模块返回唯一函数,则它不起作用的情况。为此,rewire需要类似的东西。

于 2013-03-30T23:55:26.260 回答
3

这是一个适用于 fs.promises api 的版本:

const fsMock = sinon.mock(fs.promises);
fsMock.expects('readFile').withArgs('test.json').returns(Promise.resolve(Buffer.from('{}')));

const val = await fs.promises.readFile('test.json');

expect(val.toString()).toEqual('{}');
fsMock.verify();
于 2019-06-10T14:16:40.417 回答
2

这是我的想法:

你这样做的方式是显而易见的第一步,但是必须在任何地方传递这些东西很糟糕——你的函数的调用者不应该关心你想用模拟测试。您不想只是覆盖或猴子补丁全局命名空间中的全局模块进行测试。并且正常的依赖注入模型在 Javascript 中非常冗长,因为没有类本地范围。

所以围绕整个模块,我已经完成了(function(fs, net, http) { … })(fs, net, http);

然后在模块内部,如果有一个类构造函数,则将模拟作为构造函数的可选额外参数(或单个mocks对象参数的可能属性或其他东西),并且您的测试通过模拟。您的构造函数仅覆盖模块的本地范围内的真实节点模块。

或者,如果模块只有静态功能;有一个这样的函数来初始化模拟,您可以验证该函数未在您的产品代码中调用。

于 2013-03-30T18:27:41.160 回答
2

看看using-stubs,特别是在require()部分。

像往常一样保留模块代码,例如:

//myApp.js
var fs = require('fs');

fs.readdir(path, function(err, files) {
  //Do something.
});

然后,在您的测试模块(或任何单元测试框架)上,使用using-stubs修改(甚至匹配或验证)fs的行为:

var using = require('using-stubs');

//get a reference to require('fs')
var fs = using.require('fs');

//override behaviour of fs.readdir
using(fs)('readdir').stub(function(path, callback){

    //override fs.readdir() logic
    var err = null;
    var files = [];
    // (...)

    //mock original behaviour
    callback(err, files)
})

//then run the app normally to test it (some frameworks do this for you)
require('myApp')

现在运行您的测试将覆盖myApp.js中fs的内部行为,而无需更改任何一个组件中的代码。

您还可以做其他很酷的事情,例如验证方法被调用的次数,匹配精确的方法调用参数或范围,甚至覆盖myApp.js内部使用的新 Class 实例的行为。

于 2015-12-03T11:49:57.623 回答
2

使用memfs内存文件系统。

于 2017-08-13T22:16:07.193 回答
1

查看 mock-fs 和 fake-fs,它们已经做了很多。

于 2015-03-05T19:44:38.820 回答
0
var fs = require('./myStubFs');

似乎会有很大的进步。无论您找到什么解决方案,都可能涉及编写您自己的存根函数。理想的解决方案将是较低级别的,因此您不必触摸所有您想要执行此操作的文件,例如,也许您希望 3rd 方库也被删除。

于 2013-03-30T18:29:42.187 回答
0

对我来说,不需要模拟/存根文件,我通常在临时文件夹中创建一个临时文件。

于 2020-04-29T11:00:19.697 回答