diff --git a/lib/bypass.js b/lib/bypass.js index 770e81bd..22163f08 100755 --- a/lib/bypass.js +++ b/lib/bypass.js @@ -13,43 +13,38 @@ exports = module.exports = function bypass(fn) { throw new Error(`Must provide a function to perform for mock.bypass()`); } - exports.disable(); + disable(); + let result; try { - // Perform action - const res = fn(); - - // Handle promise return - if (typeof res.then === 'function') { - res.then(exports.enable); - res.catch(exports.enable); + result = fn(); + } finally { + if (result && typeof result.finally === 'function') { + result.finally(enable); } else { - exports.enable(); + enable(); } - - return res; - } catch (e) { - exports.enable(); - throw e; } + + return result; }; /** * Temporarily disable Mocked FS */ -exports.disable = () => { +function disable() { if (realBinding._mockedBinding) { storedBinding = realBinding._mockedBinding; delete realBinding._mockedBinding; } -}; +} /** - * Enables Mocked FS after being disabled by mock.disable() + * Enables Mocked FS after being disabled by disable() */ -exports.enable = () => { +function enable() { if (storedBinding) { realBinding._mockedBinding = storedBinding; storedBinding = undefined; } -}; +} diff --git a/lib/loader.js b/lib/loader.js index f2d549dc..f8442e4a 100755 --- a/lib/loader.js +++ b/lib/loader.js @@ -10,7 +10,7 @@ const createContext = ({output, options = {}, target}, newContext) => // Assign options and set defaults if needed options: { recursive: options.recursive !== false, - lazyLoad: options.lazyLoad !== false + lazy: options.lazy !== false }, output, target @@ -20,7 +20,7 @@ const createContext = ({output, options = {}, target}, newContext) => function addFile(context, stats, isRoot) { const {output, target} = context; - const {lazyLoad} = context.options; + const {lazy} = context.options; if (!stats.isFile()) { throw new Error(`${target} is not a valid file!`); @@ -29,10 +29,10 @@ function addFile(context, stats, isRoot) { const outputPropKey = isRoot ? target : path.basename(target); output[outputPropKey] = () => { - const content = !lazyLoad ? fs.readFileSync(target) : ''; + const content = !lazy ? fs.readFileSync(target) : ''; const file = FileSystem.file(Object.assign({}, stats, {content}))(); - if (lazyLoad) { + if (lazy) { Object.defineProperty(file, '_content', { get() { const res = bypass(() => fs.readFileSync(target)); diff --git a/readme.md b/readme.md index 15901b21..e56342b2 100644 --- a/readme.md +++ b/readme.md @@ -67,13 +67,13 @@ You can load real files and directories into the mock system using `mock.load()` #### Notes - All stat information is duplicated (dates, permissions, etc) -- By default, all files are lazy-loaded, unless you specify the `{ lazyLoad: false }` option +- By default, all files are lazy-loaded, unless you specify the `{lazy: false}` option #### options | Option | Type | Default | Description | | --------- | ------- | ------- | ------------ -| lazyLoad | boolean | true | File content isn't loaded until explicitly read +| lazy | boolean | true | File content isn't loaded until explicitly read | recursive | boolean | true | Load all files and directories recursively #### `mock.load(path, options)` @@ -84,13 +84,13 @@ mock({ 'my-file.txt': mock.load(path.resolve(__dirname, 'assets/special-file.txt')), // Pre-load js file - 'ready.js': mock.load(path.resolve(__dirname, 'scripts/ready.js'), { lazyLoad: false }), + 'ready.js': mock.load(path.resolve(__dirname, 'scripts/ready.js'), {lazy: false}), // Recursively loads all node_modules 'node_modules': mock.load(path.resolve(__dirname, '../node_modules')), // Creates a directory named /tmp with only the files in /tmp/special_tmp_files (no subdirectories), pre-loading all content - '/tmp': mock.load('/tmp/special_tmp_files', { recursive: false, lazyLoad:false }), + '/tmp': mock.load('/tmp/special_tmp_files', {recursive: false, lazy:false}), 'fakefile.txt': 'content here' }); @@ -237,6 +237,8 @@ const realFilePath = '/path/to/real/file.txt'; const myData = mock.bypass(() => fs.readFileSync(realFilePath, 'utf-8')); ``` +If you pass an asynchronous function or a promise-returning function to `bypass()`, a promise will be returned. + #### Async Warning Asynchronous calls are supported, however, they are not recommended as they could produce unintended consequences if @@ -247,7 +249,7 @@ async function getFileInfo(fileName) { return await mock.bypass(async () => { const stats = await fs.promises.stat(fileName); const data = await fs.promises.readFile(fileName); - return { stats, data }; + return {stats, data}; }); } ``` diff --git a/test/lib/index.spec.js b/test/lib/index.spec.js index 017dcb11..802e011d 100644 --- a/test/lib/index.spec.js +++ b/test/lib/index.spec.js @@ -188,7 +188,7 @@ describe('The API', function() { describe(`mock.bypass()`, () => { afterEach(mock.restore); - it('(synchronous) bypasses mock FS & restores after', () => { + it('runs a synchronous function using the real filesystem', () => { mock({'/path/to/file': 'content'}); assert.equal(fs.readFileSync('/path/to/file', 'utf8'), 'content'); @@ -198,28 +198,63 @@ describe('The API', function() { assert.isNotOk(fs.existsSync(__filename)); }); - withPromise.it('(async) bypasses mock FS & restores after', done => { + it('handles functions that throw', () => { mock({'/path/to/file': 'content'}); + const error = new Error('oops'); + + assert.throws(() => { + mock.bypass(() => { + assert.isFalse(fs.existsSync('/path/to/file')); + throw error; + }); + }, error); + assert.equal(fs.readFileSync('/path/to/file', 'utf8'), 'content'); - assert.isNotOk(fs.existsSync(__filename)); + }); - mock.bypass(() => - fs.promises - .stat(__filename) - .then(stat => { - assert.isTrue(stat.isFile()); - return fs.promises.stat(__filename); - }) - .then(stat => assert.isTrue(stat.isFile())) - .then(() => { - setTimeout(() => { - assert.isNotOk(fs.existsSync(__filename)); - done(); - }, 0); - }) - .catch(err => done(err)) - ); + withPromise.it('runs an async function using the real filesystem', done => { + mock({'/path/to/file': 'content'}); + + assert.equal(fs.readFileSync('/path/to/file', 'utf8'), 'content'); + assert.isFalse(fs.existsSync(__filename)); + + const promise = mock.bypass(() => fs.promises.stat(__filename)); + assert.instanceOf(promise, Promise); + + promise + .then(stat => { + assert.isTrue(stat.isFile()); + assert.isFalse(fs.existsSync(__filename)); + done(); + }) + .catch(done); + }); + + withPromise.it('handles promise rejection', done => { + mock({'/path/to/file': 'content'}); + + assert.equal(fs.readFileSync('/path/to/file', 'utf8'), 'content'); + assert.isFalse(fs.existsSync(__filename)); + + const error = new Error('oops'); + + const promise = mock.bypass(() => { + assert.isTrue(fs.existsSync(__filename)); + return Promise.reject(error); + }); + assert.instanceOf(promise, Promise); + + promise + .then(() => { + done(new Error('expected rejection')); + }) + .catch(err => { + assert.equal(err, error); + + assert.equal(fs.readFileSync('/path/to/file', 'utf8'), 'content'); + done(); + }); }); }); @@ -260,7 +295,7 @@ describe('The API', function() { assert.instanceOf(file, File); assert.deepEqual(filterStats(file), filterStats(stats)); }); - describe('lazyLoad=true', () => { + describe('lazy=true', () => { let file; beforeEach(() => (file = mock.load(filePath)())); @@ -309,9 +344,9 @@ describe('The API', function() { }); }); - it('lazyLoad=false loads file content', () => { + it('lazy=false loads file content', () => { const file = mock.load(path.join(assetsPath, 'file1.txt'), { - lazyLoad: false + lazy: false })(); assert.equal( @@ -348,18 +383,18 @@ describe('The API', function() { assert.instanceOf(baseDirSubdir, Directory); assert.instanceOf(baseDirSubdir._items['file3.txt'], File); }); - it('respects lazyLoad setting', () => { + it('respects lazy setting', () => { let dir; const getFile = () => dir._items.dir._items.subdir._items['file3.txt']; - dir = mock.load(assetsPath, {recursive: true, lazyLoad: true})(); + dir = mock.load(assetsPath, {recursive: true, lazy: true})(); assert.typeOf( Object.getOwnPropertyDescriptor(getFile(), '_content').get, 'function' ); - dir = mock.load(assetsPath, {recursive: true, lazyLoad: false})(); + dir = mock.load(assetsPath, {recursive: true, lazy: false})(); assert.instanceOf( Object.getOwnPropertyDescriptor(getFile(), '_content').value, Buffer