javascript 如何在测试期间存根 node.js 内置 fs?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15721619/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I stub node.js built-in fs during testing?
提问by Bjorn
I want to stub node.js built-ins like fs
so that I don't actually make any system level file calls. The only thing I can think to do is to pass in fs
and all other built-ins as an argument to all of my functions to avoid the real fs from being used. This seems a little bit silly and creates a verbose function signature crowded with built ins as arguments.
我想存根 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.
});
}
And then during testing:
然后在测试期间:
var stubFs = {
readdir: function(path, callback) {
callback(null, []);
}
};
_findFile.(stubFs, testThing, testCallback);
There's a better way than this right?
还有比这更好的方法吗?
采纳答案by Noah
I like using rewirefor stubbing out require(...) statements
我喜欢使用rewire来剔除 require(...) 语句
Module Under test
待测模块
module-a.js
模块-a.js
var fs = require('fs')
function findFile(path, callback) {
fs.readdir(path, function(err, files) {
//Do something.
})
}
Test Code
测试代码
module-a-test.js
模块测试.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()
回答by Mrchief
Rewire and other stubbing solutions are good if the module under test is the one making calls to fs
itself. However, if the module under test uses a library which uses fs
underneath, rewire and other stubbing solution get hairy pretty quickly.
如果被测模块是调用fs
自身的模块,则重新布线和其他存根解决方案是很好的。然而,如果被测模块使用一个fs
在下面使用的库,重新布线和其他存根解决方案很快就会变得毛茸茸的。
There is a better solution now: mock-fs
现在有更好的解决方案:mock-fs
The mock-fs module allows Node's built-in
fs
module to be backed temporarily by an in-memory, mock file system. This lets you run tests against a set of mock files and directories instead of lugging around a bunch of test fixtures.
mock-fs 模块允许 Node 的内置
fs
模块由内存中的模拟文件系统临时支持。这使您可以针对一组模拟文件和目录运行测试,而不是拖着一堆测试装置。
Example (shamelessly lifted from its readme):
示例(无耻地从自述文件中提取):
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 */}
});
回答by Myrne Stol
An alternative (although I think Noah's suggestion of rewire is better):
另一种选择(虽然我认为诺亚的重新布线建议更好):
Write a wrapper around require
, named requireStubbable
or so. Put this in a module which you configure once, in test setup code. Because node caches result of require, whenever you require the requireStubbable module again, you'd get the same configured function. You could configure it so that any number of modules would be stubbed, all others would be passed on unchanged.
在 周围写一个包装器require
,命名为requireStubbable
左右。把它放在一个你配置一次的模块中,在测试设置代码中。因为节点缓存了 require 的结果,所以当你再次需要 requireStubbable 模块时,你会得到相同的配置函数。您可以对其进行配置,以便对任意数量的模块进行存根处理,而将所有其他模块原封不动地传递。
Any module which you'd want to support passing in stubs need to use the requireStubbable
function instead of regular require
though. The rewire module does not have that drawback, and instead gives control to the calling code.
但是,您希望支持传入存根的任何模块都需要使用该requireStubbable
函数而不是常规函数require
。rewire 模块没有这个缺点,而是将控制权交给调用代码。
Added April 26
4 月 26 日添加
I've never realized, but since the object (or more precisely: object reference) returned by require("fs")
is cached, you could simply do:
我从来没有意识到,但是由于require("fs")
缓存了返回的对象(或更准确地说:对象引用),您可以简单地执行以下操作:
const fs = require("fs")
fs.readFile = function (filename, cb) {
cb(null, new Buffer("fake contents"));
};
// etc
When you include this code anywhere, fs.readFile
will be pointing to the above function everywhere.
This works for stubbing out any module that is a mere collection of functions (like most built-in modules). The cases for which it doesn't work if the module returns a sole function. For this, something like rewire
would be necessary.
当你在任何地方包含这段代码时,fs.readFile
都会到处指向上面的函数。这适用于剔除仅仅是函数集合的任何模块(如大多数内置模块)。如果模块返回唯一函数,则它不起作用的情况。为此,需要类似的东西rewire
。
回答by Himanshu Teotia
Stubs are functions/programs that simulate the behaviors of components/modules. Stubs provide canned answers to function calls made during test cases.
存根是模拟组件/模块行为的函数/程序。存根为测试用例期间进行的函数调用提供预设答案。
An example can be writing a file, without actually doing so.
一个例子可以是写一个文件,但实际上并没有这样做。
var fs = require('fs')
var writeFileStub = sinon.stub(fs, 'writeFile', function (path, data, cb) {
return cb(null)
})
expect(writeFileStub).to.be.called
writeFileStub.restore()
回答by rch
Here's how i think of this:
这是我的看法:
The way you do it is the obvious first step but it sucks having to pass those things in everywhere— Callers of your functions shouldn't care that you want to test with mocks. You don't want to just overwrite or monkey-patch the global modules in the global namespace for your test. And the normal dependency injection model is pretty verbose in Javascript since there's no class-local scope.
你这样做的方式是显而易见的第一步,但不得不在任何地方传递这些东西很糟糕——你的函数的调用者不应该关心你想用模拟测试。您不想只是为您的测试覆盖或修补全局命名空间中的全局模块。并且 JavaScript 中的普通依赖注入模型非常冗长,因为没有类本地作用域。
So around the whole module, I've done like (function(fs, net, http) { …?})(fs, net, http);
所以围绕整个模块,我做了 (function(fs, net, http) { …?})(fs, net, http);
Then inside the module, if there's a class constructor, make the mocks optional extra parameters to the constructor (or possible properties of a single mocks
object parameter or something) and your test passes in the mocks. Your constructor overwrites the real node modules within only the module's local scope.
然后在模块内部,如果有一个类构造函数,则将模拟作为构造函数的可选额外参数(或单个mocks
对象参数或其他东西的可能属性),并且您的测试通过模拟。您的构造函数仅在模块的本地范围内覆盖实际节点模块。
Alternately if the module just has static functions; have one such function which initializes the mocks, you can validate that that function is not called in your prod code.
或者,如果模块只有静态函数;有一个这样的函数来初始化模拟,您可以验证该函数未在您的产品代码中调用。
回答by Carlos Ouro
Take a look at using-stubs, particularly at the require()part.
看看using-stubs,特别是在require()部分。
Leave you module code as you would do normally, eg:
像往常一样保留模块代码,例如:
//myApp.js
var fs = require('fs');
fs.readdir(path, function(err, files) {
//Do something.
});
Then, on your tests module (or any unit testing framework), use using-stubsto modify (and even match or validate) the behaviour of fs:
然后,在您的测试模块(或任何单元测试框架)上,使用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')
Now running your test will override the internal behaviour of fsin myApp.js, without needing to change code in either of the components.
现在运行测试将覆盖的内部行为FS在myApp.js,而无需改变代码在任一组件。
You can also do other cool stuff such as validating how many times methods are called, match exact method call parameters or scope, even override behaviour on new Class instances used internally in myApp.js.
你还可以做其他很酷的事情,比如验证方法被调用的次数,匹配精确的方法调用参数或范围,甚至覆盖myApp.js内部使用的新 Class 实例的行为。
回答by Marnen Laibow-Koser
Check out mock-fs and fake-fs, which do a lot of this already.
查看 mock-fs 和 fake-fs,它们已经做了很多这样的事情。
回答by Richard Nienaber
Here's a version that works with the fs.promises api:
这是一个适用于 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();
回答by Kerisnarendra
For me, mocking/stubbing a file is not needed, I usually create a temporary file in temporary folder.
对我来说,不需要模拟/存根文件,我通常在临时文件夹中创建一个临时文件。