如何在测试期间存根node.js内置fs?

Bjo*_*orn 29 javascript testing node.js

我想将node.js内置函数存储起来,fs以便我实际上不会进行任何系统级文件调用.我唯一能想到的就是传入fs和所有其他内置函数作为我所有函数的参数,以避免使用真正的fs.这似乎有点愚蠢,并创建一个冗长的函数签名,拥有内置的ins作为参数.

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.
  });
}
Run Code Online (Sandbox Code Playgroud)

然后在测试期间:

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

_findFile.(stubFs, testThing, testCallback);
Run Code Online (Sandbox Code Playgroud)

还有比这更好的方法吗?

Noa*_*oah 21

我喜欢使用重新连接来删除require(...)语句

模块正在测试中

模块a.js

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

测试代码

模块-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()
Run Code Online (Sandbox Code Playgroud)

  • 非常酷,但它有点让我觉得重新使用Module.wrapper,它是Module上未记录的内部属性.节点可以在不更新API版本的情况下更改它,并且所有测试都将失败.:/ (2认同)

Mrc*_*ief 12

如果被测模块是调用fs自身的模块,则Rewire和其他存根解决方案很好.但是,如果被测模块使用的是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 */}
});
Run Code Online (Sandbox Code Playgroud)

  • 我发现嘲笑-fs也有问题。它破坏了需求,日志记录和本机绑定。 (2认同)

Myr*_*tol 10

另一种选择(虽然我认为诺亚关于重新布线的建议更好):

写一个包装require,命名requireStubbable左右.将其放入您在测试设置代码中配置一次的模块中.因为节点缓存需要的结果,所以每当您再次需要requireStubbable模块时,您将获得相同的配置功能.您可以对其进行配置,以便任意数量的模块将被存根,所有其他模块将保持不变.

您希望支持传递存根的任何模块都需要使用该requireStubbable函数而不是常规函数require.重新布线模块没有这个缺点,而是控制调用代码.

4月26日新增

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

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

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

  • 我喜欢你的其他想法。快捷方便! (2认同)

Him*_*tia 5

存根是模拟组件/模块行为的函数/程序。存根提供测试用例中进行的函数调用的固定答案。

一个示例可以是写文件,而实际上没有这样做。

var fs = require('fs')

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

expect(writeFileStub).to.be.called  
writeFileStub.restore()  
Run Code Online (Sandbox Code Playgroud)

  • 在我看来,这应该是公认的经典答案。存根的概念,而不是使用“重新布线”进行反射,在测试中是通用的。并且会有许多库能够提供存根功能。 (2认同)