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 Dec 8, 2019
1 parent 03b58f2 commit f3b8bb8
Show file tree
Hide file tree
Showing 13 changed files with 162 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 @@ -839,6 +839,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 @@ -952,6 +953,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 contain at least one suite, 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 @@ -107,6 +107,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
17 changes: 17 additions & 0 deletions lib/mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,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 @@ -121,6 +122,7 @@ function Mocha(options) {
'color',
'delay',
'diff',
'forbidEmptySuite',
'forbidOnly',
'forbidPending',
'fullTrace',
Expand Down Expand Up @@ -778,6 +780,20 @@ 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 tests marked `only` fail the suite.
* @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 @@ -855,6 +871,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
5 changes: 5 additions & 0 deletions lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,11 @@ Runner.prototype.runSuite = function(suite, fn) {

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

if (this.forbidEmptySuite && suite.isEmpty()) {
this.fail(suite, new Error('Empty suite forbidden'));
return fn();
}

if (!total || (self.failures && suite._bail)) {
return fn();
}
Expand Down
14 changes: 14 additions & 0 deletions lib/suite.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,20 @@ Suite.prototype.bail = function(bail) {
return this;
};

/**
* Check if this suite contains no tests
*
* @private
*/
Suite.prototype.isEmpty = function() {
if (this.tests.length > 0) {
return false;
}
return this.suites.every(function(suite) {
return suite.isEmpty();
});
};

/**
* Check if this suite or its parent suite is marked as pending.
*
Expand Down
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() {});
});
69 changes: 69 additions & 0 deletions test/integration/options/forbidEmptySuite.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
'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();
});
});

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 f3b8bb8

Please sign in to comment.