Reputation: 71930
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.
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?
Upvotes: 37
Views: 22940
Reputation: 913
For me, mocking/stubbing a file is not needed, I usually create a temporary file in temporary folder.
Upvotes: 0
Reputation: 10564
Here's a version that works with the 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();
Upvotes: 4
Reputation: 11448
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.
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.
I've never realized, but since the object (or more precisely: object reference) returned by require("fs")
is cached, you could simply do:
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.
Upvotes: 11
Reputation: 2185
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()
Upvotes: 16
Reputation: 76238
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.
There is a better solution now: 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.
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 */}
});
Upvotes: 15
Reputation: 34343
I like using rewire for stubbing out require(...) statements
module-a.js
var fs = require('fs')
function findFile(path, callback) {
fs.readdir(path, function(err, files) {
//Do something.
})
}
module-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()
Upvotes: 24
Reputation: 565
Take a look at using-stubs, particularly at the require() part.
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-stubs to modify (and even match or validate) the behaviour of 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 fs in myApp.js, without needing to change code in either of the components.
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.
Upvotes: 2
Reputation: 6337
Check out mock-fs and fake-fs, which do a lot of this already.
Upvotes: 1
Reputation: 60818
var fs = require('./myStubFs');
Would seem to be a big improvement. Whatever solution you find will probably involve writing your own stub functions. The ideal solution would be lower level so you don't have to touch all the files you want doing this, e.g. perhaps you want 3rd party libraries stubbed out as well.
Upvotes: 0
Reputation: 21
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.
So around the whole module, I've done like (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.
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.
Upvotes: 2