Bjorn
Bjorn

Reputation: 71930

How do I stub node.js built-in fs during testing?

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

Answers (11)

Kerisnarendra
Kerisnarendra

Reputation: 913

For me, mocking/stubbing a file is not needed, I usually create a temporary file in temporary folder.

Upvotes: 0

Richard Nienaber
Richard Nienaber

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

Myrne Stol
Myrne Stol

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.

Added April 26

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

Himanshu Teotia
Himanshu Teotia

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

Vad
Vad

Reputation: 4099

Use memfs in-memory filesystem.

Upvotes: 2

Mrchief
Mrchief

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

Noah
Noah

Reputation: 34343

I like using rewire for stubbing out require(...) statements

Module Under test

module-a.js

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

Test Code

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

Carlos Ouro
Carlos Ouro

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

Marnen Laibow-Koser
Marnen Laibow-Koser

Reputation: 6337

Check out mock-fs and fake-fs, which do a lot of this already.

Upvotes: 1

djechlin
djechlin

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

rch
rch

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

Related Questions