Skip to content

Commit

Permalink
Merge pull request #306 from tschaub/bypass-updates
Browse files Browse the repository at this point in the history
Bypass updates
  • Loading branch information
tschaub committed Aug 9, 2020
2 parents ee50c67 + 211c57a commit f004073
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 53 deletions.
33 changes: 14 additions & 19 deletions lib/bypass.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
};
}
8 changes: 4 additions & 4 deletions lib/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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!`);
Expand All @@ -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));
Expand Down
12 changes: 7 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

#### <a id='mappingoptions'>options</a>

| 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)`
Expand All @@ -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'
});
Expand Down Expand Up @@ -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.

#### <a id='bypassasync'>Async Warning</a>

Asynchronous calls are supported, however, they are not recommended as they could produce unintended consequences if
Expand All @@ -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};
});
}
```
Expand Down
85 changes: 60 additions & 25 deletions test/lib/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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();
});
});
});

Expand Down Expand Up @@ -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)()));

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit f004073

Please # to comment.