Skip to content

Commit 0dacd1f

Browse files
authored
Add ability to unload files from require cache (redux) (#3726)
* Implemented `Mocha.unloadFile` and `Mocha#unloadFiles`. * Added feature tests, as well as ones for `Mocha#addFiles` and `Mocha#loadFiles`. * Beefed up some unrelated "mocha.js" tests, adding missing chainability tests.
1 parent 66a52f2 commit 0dacd1f

File tree

4 files changed

+139
-18
lines changed

4 files changed

+139
-18
lines changed

lib/cli/run-helpers.js

+5-6
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
*/
99

1010
const fs = require('fs');
11+
const path = require('path');
12+
const ansi = require('ansi-colors');
1113
const debug = require('debug')('mocha:cli:run:helpers');
14+
const minimatch = require('minimatch');
1215
const Context = require('../context');
13-
const path = require('path');
16+
const Mocha = require('../mocha');
1417
const utils = require('../utils');
15-
const minimatch = require('minimatch');
16-
const ansi = require('ansi-colors');
1718

1819
const cwd = (exports.cwd = process.cwd());
1920

@@ -249,9 +250,7 @@ exports.watchRun = (
249250
};
250251

251252
const purge = () => {
252-
watchFiles.forEach(file => {
253-
delete require.cache[file];
254-
});
253+
watchFiles.forEach(Mocha.unloadFile);
255254
};
256255

257256
loadAndRun();

lib/mocha.js

+38-4
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,6 @@ Mocha.prototype.ui = function(name) {
301301
};
302302

303303
/**
304-
* @summary
305304
* Loads `files` prior to execution.
306305
*
307306
* @description
@@ -310,6 +309,8 @@ Mocha.prototype.ui = function(name) {
310309
*
311310
* @private
312311
* @see {@link Mocha#addFile}
312+
* @see {@link Mocha#run}
313+
* @see {@link Mocha#unloadFiles}
313314
* @param {Function} [fn] - Callback invoked upon completion.
314315
*/
315316
Mocha.prototype.loadFiles = function(fn) {
@@ -324,6 +325,39 @@ Mocha.prototype.loadFiles = function(fn) {
324325
fn && fn();
325326
};
326327

328+
/**
329+
* Removes a previously loaded file from Node's `require` cache.
330+
*
331+
* @private
332+
* @static
333+
* @see {@link Mocha#unloadFiles}
334+
* @param {string} file - Pathname of file to be unloaded.
335+
*/
336+
Mocha.unloadFile = function(file) {
337+
delete require.cache[require.resolve(file)];
338+
};
339+
340+
/**
341+
* Unloads `files` from Node's `require` cache.
342+
*
343+
* @description
344+
* This allows files to be "freshly" reloaded, providing the ability
345+
* to reuse a Mocha instance programmatically.
346+
*
347+
* <strong>Intended for consumers &mdash; not used internally</strong>
348+
*
349+
* @public
350+
* @see {@link Mocha.unloadFile}
351+
* @see {@link Mocha#loadFiles}
352+
* @see {@link Mocha#run}
353+
* @returns {Mocha} this
354+
* @chainable
355+
*/
356+
Mocha.prototype.unloadFiles = function() {
357+
this.files.forEach(Mocha.unloadFile);
358+
return this;
359+
};
360+
327361
/**
328362
* Sets `grep` filter after escaping RegExp special characters.
329363
*
@@ -741,8 +775,7 @@ Object.defineProperty(Mocha.prototype, 'version', {
741775
*/
742776

743777
/**
744-
* @summary
745-
* Runs tests and invokes `fn()` when complete.
778+
* Runs root suite and invokes `fn()` when complete.
746779
*
747780
* @description
748781
* To run tests multiple times (or to run tests in files that are
@@ -751,6 +784,7 @@ Object.defineProperty(Mocha.prototype, 'version', {
751784
*
752785
* @public
753786
* @see {@link Mocha#loadFiles}
787+
* @see {@link Mocha#unloadFiles}
754788
* @see {@link Runner#run}
755789
* @param {DoneCB} [fn] - Callback invoked when test execution completed.
756790
* @return {Runner} runner instance
@@ -787,7 +821,7 @@ Mocha.prototype.run = function(fn) {
787821
exports.reporters.Base.hideDiff = options.hideDiff;
788822

789823
function done(failures) {
790-
fn = fn || function fn() {};
824+
fn = fn || utils.noop;
791825
if (reporter.done) {
792826
reporter.done(failures, fn);
793827
} else {

test/node-unit/mocha.spec.js

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
'use strict';
2+
3+
const path = require('path');
4+
const Mocha = require('../../lib/mocha');
5+
const utils = require('../../lib/utils');
6+
7+
describe('Mocha', function() {
8+
const opts = {reporter: utils.noop}; // no output
9+
const testFiles = [
10+
__filename,
11+
path.join(__dirname, 'cli', 'config.spec.js'),
12+
path.join(__dirname, 'cli', 'run.spec.js')
13+
];
14+
const resolvedTestFiles = testFiles.map(require.resolve);
15+
16+
describe('#addFile', function() {
17+
it('should add the given file to the files array', function() {
18+
const mocha = new Mocha(opts);
19+
mocha.addFile(__filename);
20+
expect(mocha.files, 'to have length', 1).and('to contain', __filename);
21+
});
22+
23+
it('should be chainable', function() {
24+
const mocha = new Mocha(opts);
25+
expect(mocha.addFile(__filename), 'to be', mocha);
26+
});
27+
});
28+
29+
describe('#loadFiles', function() {
30+
it('should load all files from the files array', function() {
31+
const mocha = new Mocha(opts);
32+
33+
testFiles.forEach(mocha.addFile, mocha);
34+
mocha.loadFiles();
35+
expect(require.cache, 'to have keys', resolvedTestFiles);
36+
});
37+
38+
it('should execute the optional callback if given', function() {
39+
const mocha = new Mocha(opts);
40+
expect(cb => {
41+
mocha.loadFiles(cb);
42+
}, 'to call the callback');
43+
});
44+
});
45+
46+
describe('.unloadFile', function() {
47+
it('should unload a specific file from cache', function() {
48+
const resolvedFilePath = require.resolve(__filename);
49+
require(__filename);
50+
expect(require.cache, 'to have key', resolvedFilePath);
51+
52+
Mocha.unloadFile(__filename);
53+
expect(require.cache, 'not to have key', resolvedFilePath);
54+
});
55+
});
56+
57+
describe('#unloadFiles', function() {
58+
it('should unload all test files from cache', function() {
59+
const mocha = new Mocha(opts);
60+
61+
testFiles.forEach(mocha.addFile, mocha);
62+
mocha.loadFiles();
63+
mocha.unloadFiles();
64+
expect(require.cache, 'not to have keys', resolvedTestFiles);
65+
});
66+
67+
it('should be chainable', function() {
68+
const mocha = new Mocha(opts);
69+
expect(mocha.unloadFiles(), 'to be', mocha);
70+
});
71+
});
72+
});

test/unit/mocha.spec.js

+24-8
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ describe('Mocha', function() {
2121
sandbox.stub(Mocha.prototype, 'useColors').returnsThis();
2222
sandbox.stub(utils, 'deprecate');
2323
});
24+
2425
it('should prefer "color" over "useColors"', function() {
2526
// eslint-disable-next-line no-new
2627
new Mocha({useColors: true, color: false});
@@ -70,14 +71,6 @@ describe('Mocha', function() {
7071
});
7172
});
7273

73-
describe('.addFile()', function() {
74-
it('should add the given file to the files array', function() {
75-
var mocha = new Mocha(opts);
76-
mocha.addFile('myFile.js');
77-
expect(mocha.files, 'to have length', 1).and('to contain', 'myFile.js');
78-
});
79-
});
80-
8174
describe('.invert()', function() {
8275
it('should set the invert option to true', function() {
8376
var mocha = new Mocha(opts);
@@ -153,6 +146,7 @@ describe('Mocha', function() {
153146
expect(mocha.options, 'to have property', 'growl', true);
154147
});
155148
});
149+
156150
describe('if not capable of notifications', function() {
157151
it('should set the growl option to false', function() {
158152
var mocha = new Mocha(opts);
@@ -163,6 +157,7 @@ describe('Mocha', function() {
163157
expect(mocha.options, 'to have property', 'growl', false);
164158
});
165159
});
160+
166161
it('should be chainable', function() {
167162
var mocha = new Mocha(opts);
168163
expect(mocha.growl(), 'to be', mocha);
@@ -195,11 +190,17 @@ describe('Mocha', function() {
195190
});
196191

197192
describe('.noHighlighting()', function() {
193+
// :NOTE: Browser-only option...
198194
it('should set the noHighlighting option to true', function() {
199195
var mocha = new Mocha(opts);
200196
mocha.noHighlighting();
201197
expect(mocha.options, 'to have property', 'noHighlighting', true);
202198
});
199+
200+
it('should be chainable', function() {
201+
var mocha = new Mocha(opts);
202+
expect(mocha.noHighlighting(), 'to be', mocha);
203+
});
203204
});
204205

205206
describe('.allowUncaught()', function() {
@@ -208,6 +209,11 @@ describe('Mocha', function() {
208209
mocha.allowUncaught();
209210
expect(mocha.options, 'to have property', 'allowUncaught', true);
210211
});
212+
213+
it('should be chainable', function() {
214+
var mocha = new Mocha(opts);
215+
expect(mocha.allowUncaught(), 'to be', mocha);
216+
});
211217
});
212218

213219
describe('.delay()', function() {
@@ -216,6 +222,11 @@ describe('Mocha', function() {
216222
mocha.delay();
217223
expect(mocha.options, 'to have property', 'delay', true);
218224
});
225+
226+
it('should be chainable', function() {
227+
var mocha = new Mocha(opts);
228+
expect(mocha.delay(), 'to be', mocha);
229+
});
219230
});
220231

221232
describe('.bail()', function() {
@@ -224,6 +235,11 @@ describe('Mocha', function() {
224235
mocha.bail();
225236
expect(mocha.suite._bail, 'to be', true);
226237
});
238+
239+
it('should be chainable', function() {
240+
var mocha = new Mocha(opts);
241+
expect(mocha.bail(), 'to be', mocha);
242+
});
227243
});
228244

229245
describe('error handling', function() {

0 commit comments

Comments
 (0)