Skip to content

Commit

Permalink
Add option to forbid empty test suites
Browse files Browse the repository at this point in the history
  • Loading branch information
eemeli committed Mar 13, 2020
1 parent e1389ef commit a357da0
Show file tree
Hide file tree
Showing 13 changed files with 176 additions and 0 deletions.
7 changes: 7 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,7 @@ Rules & Behavior
--check-leaks Check for global variable leaks [boolean]
--delay Delay initial execution of root suite [boolean]
--exit Force Mocha to quit after tests complete [boolean]
--forbid-empty-suite Fail if a test suite contains no tests [boolean]
--forbid-only Fail if exclusive test(s) encountered [boolean]
--forbid-pending Fail if pending test(s) encountered [boolean]
--global, --globals List of allowed global variables [array]
Expand Down Expand Up @@ -954,6 +955,12 @@ To ensure your tests aren't leaving messes around, here are some ideas to get st
- Try something like [wtfnode][npm-wtfnode]
- Use [`.only`](#exclusive-tests) until you find the test that causes Mocha to hang

### `--forbid-empty-suite`

Enforce a rule that test suites must define at least one test, either directly or in an inner suite.

`--forbid-empty-suite` causes Mocha to fail when an empty suite is encountered.

### `--forbid-only`

Enforce a rule that tests may not be exclusive (use of e.g., `describe.only()` or `it.only()` is disallowed).
Expand Down
1 change: 1 addition & 0 deletions lib/cli/run-option-metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ exports.types = {
'delay',
'diff',
'exit',
'forbid-empty-suite',
'forbid-only',
'forbid-pending',
'full-trace',
Expand Down
4 changes: 4 additions & 0 deletions lib/cli/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ exports.builder = yargs =>
normalize: true,
requiresArg: true
},
'forbid-empty-suite': {
description: 'Fail if a test suite contains no tests',
group: GROUPS.RULES
},
'forbid-only': {
description: 'Fail if exclusive test(s) encountered',
group: GROUPS.RULES
Expand Down
18 changes: 18 additions & 0 deletions lib/mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ exports.Test = require('./test');
* @param {boolean} [options.delay] - Delay root suite execution?
* @param {boolean} [options.diff] - Show diff on failure?
* @param {string} [options.fgrep] - Test filter given string.
* @param {boolean} [options.forbidEmptySuite] - Require at least one test.
* @param {boolean} [options.forbidOnly] - Tests marked `only` fail the suite?
* @param {boolean} [options.forbidPending] - Pending tests fail the suite?
* @param {boolean} [options.fullTrace] - Full stacktrace upon failure?
Expand Down Expand Up @@ -125,6 +126,7 @@ function Mocha(options) {
'color',
'delay',
'diff',
'forbidEmptySuite',
'forbidOnly',
'forbidPending',
'fullTrace',
Expand Down Expand Up @@ -828,6 +830,21 @@ Mocha.prototype.delay = function delay() {
return this;
};

/**
* Causes running a suite with no tests to fail it.
*
* @public
* @see [CLI option](../#-forbid-empty-suite)
* @param {boolean} [forbidEmptySuite=true] - Whether each suite is required to
* define at least one test.
* @returns {Mocha} this
* @chainable
*/
Mocha.prototype.forbidEmptySuite = function(forbidEmptySuite) {
this.options.forbidEmptySuite = forbidEmptySuite !== false;
return this;
};

/**
* Causes tests marked `only` to fail the suite.
*
Expand Down Expand Up @@ -909,6 +926,7 @@ Mocha.prototype.run = function(fn) {
runner.fullStackTrace = options.fullTrace;
runner.asyncOnly = options.asyncOnly;
runner.allowUncaught = options.allowUncaught;
runner.forbidEmptySuite = options.forbidEmptySuite;
runner.forbidOnly = options.forbidOnly;
runner.forbidPending = options.forbidPending;
if (options.grep) {
Expand Down
3 changes: 3 additions & 0 deletions lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,9 @@ Runner.prototype.runSuite = function(suite, fn) {

debug('run suite %s', suite.fullTitle());

if (!total && this.forbidEmptySuite) {
this.fail(suite, new Error('Empty suite forbidden'));
}
if (!total || (self.failures && suite._bail)) {
return fn();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict';

describe('suite with dynamically added test', function() {
const suite = this;
before(function() {
suite.suites[1].addTest(it('added test', function() {}));
});

describe('A', function() {
it('existing test', () => {});
});

describe('B', function() {});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use strict';

describe('parent suite', function() {
describe('suite with test', function() {
it('it nested', function() {});
});
describe('empty suite', function() {});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict';

describe('forbid empty suite - empty', function() {});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
'use strict';
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

describe('parent suite', function() {
describe('suite with test', function() {
it('it nested', function() {});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

describe('forbid empty suite - not empty', function() {
it('test1', function() {});
it('test2', function() {});
it('test3', function() {});
});
84 changes: 84 additions & 0 deletions test/integration/options/forbidEmptySuite.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
'use strict';

var path = require('path').posix;
var helpers = require('../helpers');
var runMocha = helpers.runMocha;
var runMochaJSON = helpers.runMochaJSON;

describe('--forbid-empty-suite', function() {
var args = [];
var emptySuiteErrorMessage = 'Empty suite forbidden';

before(function() {
args = ['--forbid-empty-suite'];
});

it('should succeed if there are tests', function(done) {
var fixture = path.join('options', 'forbid-empty-suite', 'passed');
runMochaJSON(fixture, args, function(err, res) {
if (err) {
return done(err);
}
expect(res, 'to have passed');
done();
});
});

it('should succeed if there is an inner suite with tests', function(done) {
var fixture = path.join('options', 'forbid-empty-suite', 'nested-suite');
runMochaJSON(fixture, args, function(err, res) {
if (err) {
return done(err);
}
expect(res, 'to have passed');
done();
});
});

it('should succeed if there are dynamically added tests', function(done) {
var fixture = path.join(
'options',
'forbid-empty-suite',
'dynamically-added-test'
);
runMochaJSON(fixture, args, function(err, res) {
if (err) {
return done(err);
}
expect(res, 'to have passed');
done();
});
});

var forbidEmptySuiteFailureTests = {
'should fail if there are no test suites': 'empty',
'should fail if there are no tests': 'empty-suite',
'should fail if there is an inner suite with no tests': 'empty-nested-suite'
};

Object.keys(forbidEmptySuiteFailureTests).forEach(function(title) {
it(title, function(done) {
var fixture = path.join(
'options',
'forbid-empty-suite',
forbidEmptySuiteFailureTests[title]
);
var spawnOpts = {stdio: 'pipe'};
runMocha(
fixture,
args,
function(err, res) {
if (err) {
return done(err);
}
expect(res, 'to satisfy', {
code: 1,
output: new RegExp(emptySuiteErrorMessage)
});
done();
},
spawnOpts
);
});
});
});
19 changes: 19 additions & 0 deletions test/unit/mocha.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,25 @@ describe('Mocha', function() {
});
});

describe('#forbidEmptySuite()', function() {
it('should set the forbidEmptySuite option to true', function() {
var mocha = new Mocha(opts);
mocha.forbidEmptySuite();
expect(mocha.options, 'to have property', 'forbidEmptySuite', true);
});

it('should set the forbidEmptySuite option to false', function() {
var mocha = new Mocha(opts);
mocha.forbidEmptySuite(false);
expect(mocha.options, 'to have property', 'forbidEmptySuite', false);
});

it('should be chainable', function() {
var mocha = new Mocha(opts);
expect(mocha.forbidEmptySuite(), 'to be', mocha);
});
});

describe('#forbidOnly()', function() {
it('should set the forbidOnly option to true', function() {
var mocha = new Mocha(opts);
Expand Down

0 comments on commit a357da0

Please sign in to comment.