Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bypass updates #306

Merged
merged 3 commits into from
Aug 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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