diff --git a/docs/index.md b/docs/index.md
index 1cc8047687..12b4b18955 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -874,6 +874,12 @@ Use this option to have Mocha check for global variables that are leaked while r
> _`--compilers` was removed in v6.0.0. See [further explanation and workarounds][mocha-wiki-compilers]._
+### `--dry-run`
+
+> _New in v9.0.0._
+
+Report tests without executing any of them, neither tests nor hooks.
+
### `--exit`
> _Updated in v4.0.0._
@@ -2102,6 +2108,7 @@ mocha.setup({
asyncOnly: true,
bail: true,
checkLeaks: true,
+ dryRun: true,
forbidOnly: true,
forbidPending: true,
global: ['MyLib'],
diff --git a/lib/cli/run-option-metadata.js b/lib/cli/run-option-metadata.js
index 33cd15ae08..fc870992c9 100644
--- a/lib/cli/run-option-metadata.js
+++ b/lib/cli/run-option-metadata.js
@@ -32,6 +32,7 @@ const TYPES = (exports.types = {
'color',
'delay',
'diff',
+ 'dry-run',
'exit',
'forbid-only',
'forbid-pending',
diff --git a/lib/cli/run.js b/lib/cli/run.js
index a8c8b619b3..79903327c3 100644
--- a/lib/cli/run.js
+++ b/lib/cli/run.js
@@ -83,6 +83,10 @@ exports.builder = yargs =>
description: 'Show diff on failure',
group: GROUPS.OUTPUT
},
+ 'dry-run': {
+ description: 'Report tests without executing them',
+ group: GROUPS.RULES
+ },
exit: {
description: 'Force Mocha to quit after tests complete',
group: GROUPS.RULES
diff --git a/lib/mocha.js b/lib/mocha.js
index f1f2e25dd7..62a414a9fa 100644
--- a/lib/mocha.js
+++ b/lib/mocha.js
@@ -30,7 +30,6 @@ const {
EVENT_FILE_POST_REQUIRE,
EVENT_FILE_REQUIRE
} = Suite.constants;
-var sQuote = utils.sQuote;
var debug = require('debug')('mocha:mocha');
exports = module.exports = Mocha;
@@ -164,6 +163,7 @@ exports.run = function(...args) {
* @param {boolean} [options.color] - Color TTY output from reporter?
* @param {boolean} [options.delay] - Delay root suite execution?
* @param {boolean} [options.diff] - Show diff on failure?
+ * @param {boolean} [options.dryRun] - Report tests without running them?
* @param {string} [options.fgrep] - Test filter given string.
* @param {boolean} [options.forbidOnly] - Tests marked `only` fail the suite?
* @param {boolean} [options.forbidPending] - Pending tests fail the suite?
@@ -200,7 +200,7 @@ function Mocha(options = {}) {
.ui(options.ui)
.reporter(
options.reporter,
- options.reporterOption || options.reporterOptions // reporterOptions was previously the only way to specify options to reporter
+ options.reporterOption || options.reporterOptions // for backwards compability
)
.slow(options.slow)
.global(options.global);
@@ -222,6 +222,7 @@ function Mocha(options = {}) {
'color',
'delay',
'diff',
+ 'dryRun',
'forbidOnly',
'forbidPending',
'fullTrace',
@@ -346,23 +347,19 @@ Mocha.prototype.reporter = function(reporterName, reporterOptions) {
reporter = require(path.resolve(utils.cwd(), reporterName));
} catch (_err) {
_err.code === 'MODULE_NOT_FOUND'
- ? warn(sQuote(reporterName) + ' reporter not found')
+ ? warn(`'${reporterName}' reporter not found`)
: warn(
- sQuote(reporterName) +
- ' reporter blew up with error:\n' +
- err.stack
+ `'${reporterName}' reporter blew up with error:\n ${err.stack}`
);
}
} else {
- warn(
- sQuote(reporterName) + ' reporter blew up with error:\n' + err.stack
- );
+ warn(`'${reporterName}' reporter blew up with error:\n ${err.stack}`);
}
}
}
if (!reporter) {
throw createInvalidReporterError(
- 'invalid reporter ' + sQuote(reporterName),
+ `invalid reporter '${reporterName}'`,
reporterName
);
}
@@ -396,10 +393,7 @@ Mocha.prototype.ui = function(ui) {
try {
bindInterface = require(ui);
} catch (err) {
- throw createInvalidInterfaceError(
- 'invalid interface ' + sQuote(ui),
- ui
- );
+ throw createInvalidInterfaceError(`invalid interface '${ui}'`, ui);
}
}
}
@@ -784,6 +778,20 @@ Mocha.prototype.diff = function(diff) {
return this;
};
+/**
+ * Enables or disables running tests in dry-run mode.
+ *
+ * @public
+ * @see [CLI option](../#-dry-run)
+ * @param {boolean} [dryRun=true] - Whether to activate dry-run mode.
+ * @return {Mocha} this
+ * @chainable
+ */
+Mocha.prototype.dryRun = function(dryRun) {
+ this.options.dryRun = dryRun !== false;
+ return this;
+};
+
/**
* @summary
* Sets timeout threshold value.
@@ -1016,6 +1024,7 @@ Mocha.prototype.run = function(fn) {
options.files = this.files;
const runner = new this._runnerClass(suite, {
delay: options.delay,
+ dryRun: options.dryRun,
cleanReferencesAfterRun: this._cleanReferencesAfterRun
});
createStatsCollector(runner);
diff --git a/lib/runner.js b/lib/runner.js
index f493004aea..079ab3066d 100644
--- a/lib/runner.js
+++ b/lib/runner.js
@@ -4,7 +4,6 @@
* Module dependencies.
* @private
*/
-var util = require('util');
var EventEmitter = require('events').EventEmitter;
var Pending = require('./pending');
var utils = require('./utils');
@@ -19,8 +18,6 @@ var EVENT_ROOT_SUITE_RUN = Suite.constants.EVENT_ROOT_SUITE_RUN;
var STATE_FAILED = Runnable.constants.STATE_FAILED;
var STATE_PASSED = Runnable.constants.STATE_PASSED;
var STATE_PENDING = Runnable.constants.STATE_PENDING;
-var dQuote = utils.dQuote;
-var sQuote = utils.sQuote;
var stackFilter = utils.stackTraceFilter();
var stringify = utils.stringify;
@@ -140,6 +137,7 @@ class Runner extends EventEmitter {
* @param {Suite} suite - Root suite
* @param {Object|boolean} [opts] - Options. If `boolean` (deprecated), whether or not to delay execution of root suite until ready.
* @param {boolean} [opts.delay] - Whether to delay execution of root suite until ready.
+ * @param {boolean} [opts.dryRun] - Whether to report tests without running them.
* @param {boolean} [opts.cleanReferencesAfterRun] - Whether to clean references to test fns and hooks when a suite is done.
*/
constructor(suite, opts) {
@@ -410,9 +408,8 @@ Runner.prototype.checkGlobals = function(test) {
this._globals = this._globals.concat(leaks);
if (leaks.length) {
- var msg = 'global leak(s) detected: %s';
- var error = new Error(util.format(msg, leaks.map(sQuote).join(', ')));
- this.fail(test, error);
+ var msg = `global leak(s) detected: ${leaks.map(e => `'${e}'`).join(', ')}`;
+ this.fail(test, new Error(msg));
}
};
@@ -479,6 +476,8 @@ Runner.prototype.fail = function(test, err, force) {
*/
Runner.prototype.hook = function(name, fn) {
+ if (this._opts.dryRun) return fn();
+
var suite = this.suite;
var hooks = suite.getHooks(name);
var self = this;
@@ -557,8 +556,7 @@ Runner.prototype.hook = function(name, fn) {
function setHookTitle(hook) {
hook.originalTitle = hook.originalTitle || hook.title;
if (hook.ctx && hook.ctx.currentTest) {
- hook.title =
- hook.originalTitle + ' for ' + dQuote(hook.ctx.currentTest.title);
+ hook.title = `${hook.originalTitle} for "${hook.ctx.currentTest.title}"`;
} else {
var parentTitle;
if (hook.parent.title) {
@@ -566,7 +564,7 @@ Runner.prototype.hook = function(name, fn) {
} else {
parentTitle = hook.parent.root ? '{root}' : '';
}
- hook.title = hook.originalTitle + ' in ' + dQuote(parentTitle);
+ hook.title = `${hook.originalTitle} in "${parentTitle}"`;
}
}
}
@@ -612,7 +610,7 @@ Runner.prototype.hooks = function(name, suites, fn) {
};
/**
- * Run hooks from the top level down.
+ * Run 'afterEach' hooks from bottom up.
*
* @param {String} name
* @param {Function} fn
@@ -624,7 +622,7 @@ Runner.prototype.hookUp = function(name, fn) {
};
/**
- * Run hooks from the bottom up.
+ * Run 'beforeEach' hooks from top level down.
*
* @param {String} name
* @param {Function} fn
@@ -659,6 +657,8 @@ Runner.prototype.parents = function() {
* @private
*/
Runner.prototype.runTest = function(fn) {
+ if (this._opts.dryRun) return fn();
+
var self = this;
var test = this.test;
@@ -704,7 +704,6 @@ Runner.prototype.runTests = function(suite, fn) {
self.suite = after ? errSuite.parent : errSuite;
if (self.suite) {
- // call hookUp afterEach
self.hookUp(HOOK_TYPE_AFTER_EACH, function(err2, errSuite2) {
self.suite = orig;
// some hooks may fail even now
diff --git a/lib/utils.js b/lib/utils.js
index e46eac8d1a..9120347583 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -518,44 +518,6 @@ exports.clamp = function clamp(value, range) {
return Math.min(Math.max(value, range[0]), range[1]);
};
-/**
- * Single quote text by combining with undirectional ASCII quotation marks.
- *
- * @description
- * Provides a simple means of markup for quoting text to be used in output.
- * Use this to quote names of variables, methods, and packages.
- *
- * package 'foo' cannot be found
- *
- * @private
- * @param {string} str - Value to be quoted.
- * @returns {string} quoted value
- * @example
- * sQuote('n') // => 'n'
- */
-exports.sQuote = function(str) {
- return "'" + str + "'";
-};
-
-/**
- * Double quote text by combining with undirectional ASCII quotation marks.
- *
- * @description
- * Provides a simple means of markup for quoting text to be used in output.
- * Use this to quote names of datatypes, classes, pathnames, and strings.
- *
- * argument 'value' must be "string" or "number"
- *
- * @private
- * @param {string} str - Value to be quoted.
- * @returns {string} quoted value
- * @example
- * dQuote('number') // => "number"
- */
-exports.dQuote = function(str) {
- return '"' + str + '"';
-};
-
/**
* It's a noop.
* @public
diff --git a/test/integration/fixtures/options/dry-run/dry-run.fixture.js b/test/integration/fixtures/options/dry-run/dry-run.fixture.js
new file mode 100644
index 0000000000..ebb48ad1fa
--- /dev/null
+++ b/test/integration/fixtures/options/dry-run/dry-run.fixture.js
@@ -0,0 +1,35 @@
+'use strict';
+
+describe.only('suite1', function() {
+ it.skip('test1 - report as skipped', function() { });
+
+ it('test2 - report as passed', function() { });
+
+ it('test3 - report as passed', function() {
+ throw new Error('this test should not run');
+ });
+});
+
+describe('suite2', function () {
+ before(function() {
+ throw new Error('this hook should not run');
+ });
+ beforeEach(function() {
+ throw new Error('this hook should not run');
+ });
+
+ it.only('test4 - report as passed', function () {
+ throw new Error('this test should not run');
+ });
+
+ it('test5 - should be ignored', function () {
+ throw new Error('this test should not run');
+ });
+
+ afterEach(function() {
+ throw new Error('this hook should not run');
+ });
+ after(function() {
+ throw new Error('this hook should not run');
+ });
+});
diff --git a/test/integration/options/dryRun.spec.js b/test/integration/options/dryRun.spec.js
new file mode 100644
index 0000000000..81325948ee
--- /dev/null
+++ b/test/integration/options/dryRun.spec.js
@@ -0,0 +1,30 @@
+'use strict';
+
+var path = require('path').posix;
+var helpers = require('../helpers');
+var runMochaJSON = helpers.runMochaJSON;
+
+describe('--dry-run', function() {
+ var args = ['--dry-run'];
+
+ it('should only report, but not execute any test', function(done) {
+ var fixture = path.join('options/dry-run', 'dry-run');
+ runMochaJSON(fixture, args, function(err, res) {
+ if (err) {
+ return done(err);
+ }
+
+ expect(res, 'to have passed')
+ .and(
+ 'to have passed tests',
+ 'test2 - report as passed',
+ 'test3 - report as passed',
+ 'test4 - report as passed'
+ )
+ .and('to have passed test count', 3)
+ .and('to have pending test count', 1)
+ .and('to have failed test count', 0);
+ done();
+ });
+ });
+});
diff --git a/test/integration/reporters.spec.js b/test/integration/reporters.spec.js
index 944f94f552..17bbcd2f41 100644
--- a/test/integration/reporters.spec.js
+++ b/test/integration/reporters.spec.js
@@ -5,8 +5,6 @@ var fs = require('fs');
var crypto = require('crypto');
var path = require('path');
var run = require('./helpers').runMocha;
-var utils = require('../../lib/utils');
-var dQuote = utils.dQuote;
describe('reporters', function() {
describe('markdown', function() {
@@ -215,9 +213,7 @@ describe('reporters', function() {
return;
}
- var pattern =
- '^Error: invalid or unsupported TAP version: ' +
- dQuote(invalidTapVersion);
+ var pattern = `^Error: invalid or unsupported TAP version: "${invalidTapVersion}"`;
expect(res, 'to satisfy', {
code: 1,
output: new RegExp(pattern, 'm')
diff --git a/test/reporters/json-stream.spec.js b/test/reporters/json-stream.spec.js
index 58694e3f19..f4610200c1 100644
--- a/test/reporters/json-stream.spec.js
+++ b/test/reporters/json-stream.spec.js
@@ -3,11 +3,9 @@
var events = require('../../').Runner.constants;
var helpers = require('./helpers');
var reporters = require('../../').reporters;
-var utils = require('../../lib/utils');
var JSONStream = reporters.JSONStream;
var createMockRunner = helpers.createMockRunner;
-var dQuote = utils.dQuote;
var makeExpectedTest = helpers.makeExpectedTest;
var makeRunReporter = helpers.createRunReporterFunction;
@@ -71,17 +69,17 @@ describe('JSON Stream reporter', function() {
stdout[0],
'to equal',
'["pass",{"title":' +
- dQuote(expectedTitle) +
+ `"${expectedTitle}"` +
',"fullTitle":' +
- dQuote(expectedFullTitle) +
+ `"${expectedFullTitle}"` +
',"file":' +
- dQuote(expectedFile) +
+ `"${expectedFile}"` +
',"duration":' +
expectedDuration +
',"currentRetry":' +
currentRetry +
',"speed":' +
- dQuote(expectedSpeed) +
+ `"${expectedSpeed}"` +
'}]\n'
);
});
@@ -106,21 +104,21 @@ describe('JSON Stream reporter', function() {
stdout[0],
'to equal',
'["fail",{"title":' +
- dQuote(expectedTitle) +
+ `"${expectedTitle}"` +
',"fullTitle":' +
- dQuote(expectedFullTitle) +
+ `"${expectedFullTitle}"` +
',"file":' +
- dQuote(expectedFile) +
+ `"${expectedFile}"` +
',"duration":' +
expectedDuration +
',"currentRetry":' +
currentRetry +
',"speed":' +
- dQuote(expectedSpeed) +
+ `"${expectedSpeed}"` +
',"err":' +
- dQuote(expectedErrorMessage) +
+ `"${expectedErrorMessage}"` +
',"stack":' +
- dQuote(expectedErrorStack) +
+ `"${expectedErrorStack}"` +
'}]\n'
);
});
@@ -144,19 +142,19 @@ describe('JSON Stream reporter', function() {
stdout[0],
'to equal',
'["fail",{"title":' +
- dQuote(expectedTitle) +
+ `"${expectedTitle}"` +
',"fullTitle":' +
- dQuote(expectedFullTitle) +
+ `"${expectedFullTitle}"` +
',"file":' +
- dQuote(expectedFile) +
+ `"${expectedFile}"` +
',"duration":' +
expectedDuration +
',"currentRetry":' +
currentRetry +
',"speed":' +
- dQuote(expectedSpeed) +
+ `"${expectedSpeed}"` +
',"err":' +
- dQuote(expectedErrorMessage) +
+ `"${expectedErrorMessage}"` +
',"stack":null}]\n'
);
});
diff --git a/test/unit/mocha.spec.js b/test/unit/mocha.spec.js
index 142018b54c..35f7abd327 100644
--- a/test/unit/mocha.spec.js
+++ b/test/unit/mocha.spec.js
@@ -352,6 +352,20 @@ describe('Mocha', function() {
});
});
+ describe('dryRun()', function() {
+ it('should set the dryRun option to true', function() {
+ mocha.dryRun();
+ expect(mocha.options, 'to have property', 'dryRun', true);
+ });
+
+ describe('when provided `false` argument', function() {
+ it('should set the dryRun option to false', function() {
+ mocha.dryRun(false);
+ expect(mocha.options, 'to have property', 'dryRun', false);
+ });
+ });
+ });
+
describe('dispose()', function() {
it('should dispose the root suite', function() {
mocha.dispose();
diff --git a/test/unit/utils.spec.js b/test/unit/utils.spec.js
index 53d4f20f32..9e4068b9bf 100644
--- a/test/unit/utils.spec.js
+++ b/test/unit/utils.spec.js
@@ -669,24 +669,6 @@ describe('lib/utils', function() {
});
});
- describe('sQuote()', function() {
- var str = 'xxx';
-
- it('should return its input as string wrapped in single quotes', function() {
- var expected = "'xxx'";
- expect(utils.sQuote(str), 'to be', expected);
- });
- });
-
- describe('dQuote()', function() {
- var str = 'xxx';
-
- it('should return its input as string wrapped in double quotes', function() {
- var expected = '"xxx"';
- expect(utils.dQuote(str), 'to be', expected);
- });
- });
-
describe('createMap()', function() {
it('should return an object with a null prototype', function() {
expect(Object.getPrototypeOf(utils.createMap()), 'to be', null);