Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
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
2 changes: 2 additions & 0 deletions docs/api-tutorials/custom-reporter.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ The event names are exported from the `constants` property of `Mocha.Runner`:
| `EVENT_TEST_PENDING` | `pending` | `Test` | A {@link Test} was skipped. |
| `EVENT_TEST_RETRY` | `retry` | `Test`, `Error` | A {@link Test} failed, but is about to be retried; only emitted if the `retry` option is nonzero. |

| `EVENT_TEST_REPEAT` | `repeat` | `Test`, `Error` | A {@link Test} succeeded, but is about to be repeated; only emitted if the `repeat` option is nonzero. |

**Please use these constants** instead of the event names in your own reporter! This will ensure compatibility with future versions of Mocha.

> It's important to understand that all `Suite` callbacks will be run _before_ the {@link Runner} emits `EVENT_RUN_BEGIN`. Hooks and tests won't run until _after_ the {@link Runner} emits `EVENT_RUN_BEGIN`.
26 changes: 26 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,31 @@ describe("retries", function () {
});
```

## Repeat Tests

Tests can also be repeated when they pass. This feature can be used to test for leaks and proper tear-down procedures. In this case a test is considered to be successful only if all the runs are successful.

This feature does re-run a passed test and its corresponding `beforeEach/afterEach` hooks, but not `before/after` hooks.

If using both `repeat` and `retries`, the test will be run `repeat` times tolerating up to `retries` failures in total.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Praise] Very good line to have. The distinction between the repeat and retries could be tricky for folks to understand. I like having this clear explanation!


```js
describe('repeat', function () {
// Repeat all tests in this suite 4 times
this.repeats(4);

beforeEach(function () {
browser.get('http://www.yahoo.com');
});

it('should use proper tear-down', function () {
// Specify that only this test is to be repeated twice (and not 4x2=8 times)
this.repeats(2);
expect($('.foo').isDisplayed()).to.eventually.be.true;
});
});
```

## Dynamically Generating Tests

Given Mocha's use of function expressions to define suites and test cases, it's straightforward to generate your tests dynamically. No special syntax is required — plain ol' JavaScript can be used to achieve functionality similar to "parameterized" tests, which you may have seen in other frameworks.
Expand Down Expand Up @@ -2218,6 +2243,7 @@ mocha.setup({
forbidPending: true,
global: ['MyLib'],
retries: 3,
repeats: 1,
rootHooks: { beforeEach(done) { ... done();} },
slow: '100',
timeout: '2000',
Expand Down
1 change: 1 addition & 0 deletions example/config/.mocharc.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ module.exports = {
"reporter-option": ["foo=bar", "baz=quux"], // array, not object
require: "@babel/register",
retries: 1,
repeats: 1,
slow: "75",
sort: false,
spec: ["test/**/*.spec.js"], // the positional arguments!
Expand Down
1 change: 1 addition & 0 deletions example/config/.mocharc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ reporter-option: # array, not object
- "baz=quux"
require: "@babel/register"
retries: 1
repeats: 1
slow: "75"
sort: false
spec:
Expand Down
2 changes: 1 addition & 1 deletion lib/cli/run-option-metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const TYPES = (exports.types = {
"sort",
"watch",
],
number: ["retries", "jobs"],
number: ["retries", "repeats", "jobs"],
string: [
"config",
"fgrep",
Expand Down
4 changes: 4 additions & 0 deletions lib/cli/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,10 @@ exports.builder = (yargs) =>
description: "Retry failed tests this many times",
group: GROUPS.RULES,
},
repeats: {
description: 'Repeat passed tests this many times',
group: GROUPS.RULES
},
slow: {
default: defaults.slow,
description: 'Specify "slow" test threshold (in milliseconds)',
Expand Down
15 changes: 15 additions & 0 deletions lib/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,18 @@ Context.prototype.retries = function (n) {
this.runnable().retries(n);
return this;
};

/**
* Set or get a number of repeats on passed tests
*
* @private
* @param {number} n
* @return {Context} self
*/
Context.prototype.repeats = function (n) {
if (!arguments.length) {
return this.runnable().repeats();
}
this.runnable().repeats(n);
return this;
};
1 change: 1 addition & 0 deletions lib/hook.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Hook.prototype.error = function (err) {
Hook.prototype.serialize = function serialize() {
return {
$$currentRetry: this.currentRetry(),
$$currentRepeat: this.currentRepeat(),
$$fullTitle: this.fullTitle(),
$$isPending: Boolean(this.isPending()),
$$titlePath: this.titlePath(),
Expand Down
23 changes: 23 additions & 0 deletions lib/mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ function Mocha(options = {}) {
this.retries(options.retries);
}

if ("repeats" in options) {
this.repeats(options.repeats);
}

[
"allowUncaught",
"asyncOnly",
Expand Down Expand Up @@ -745,6 +749,25 @@ Mocha.prototype.retries = function (retry) {
return this;
};

/**
* Sets the number of times to repeat passed tests.
*
* @public
* @see [CLI option](../#-repeats-n)
* @see [Repeat Tests](../#repeat-tests)
* @param {number} repeats - Number of times to repeat passed tests.
* @return {Mocha} this
* @chainable
* @example
*
* // Allow any passed test to be repeated multiple times
* mocha.repeats(1);
*/
Mocha.prototype.repeats = function (repeats) {
this.suite.repeats(repeats);
return this;
};

/**
* Sets slowness threshold value.
*
Expand Down
2 changes: 2 additions & 0 deletions lib/nodejs/reporters/parallel-buffered.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const {
EVENT_TEST_BEGIN,
EVENT_TEST_END,
EVENT_TEST_RETRY,
EVENT_TEST_REPEAT,
EVENT_DELAY_BEGIN,
EVENT_DELAY_END,
EVENT_HOOK_BEGIN,
Expand All @@ -49,6 +50,7 @@ const EVENT_NAMES = [
EVENT_TEST_FAIL,
EVENT_TEST_PASS,
EVENT_TEST_RETRY,
EVENT_TEST_REPEAT,
EVENT_TEST_END,
EVENT_HOOK_BEGIN,
EVENT_HOOK_END,
Expand Down
1 change: 1 addition & 0 deletions lib/reporters/json-stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ function clean(test) {
file: test.file,
duration: test.duration,
currentRetry: test.currentRetry(),
currentRepeat: test.currentRepeat(),
speed: test.speed,
};
}
Expand Down
1 change: 1 addition & 0 deletions lib/reporters/json.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ function clean(test) {
file: test.file,
duration: test.duration,
currentRetry: test.currentRetry(),
currentRepeat: test.currentRepeat(),
speed: test.speed,
err: cleanCycles(err),
};
Expand Down
26 changes: 26 additions & 0 deletions lib/runnable.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ function Runnable(title, fn) {
this._timeout = 2000;
this._slow = 75;
this._retries = -1;
this._repeats = 1;
utils.assignNewMochaID(this);
Object.defineProperty(this, "id", {
get() {
Expand All @@ -64,6 +65,7 @@ utils.inherits(Runnable, EventEmitter);
Runnable.prototype.reset = function () {
this.timedOut = false;
this._currentRetry = 0;
this._currentRepeat = 1;
this.pending = false;
delete this.state;
delete this.err;
Expand Down Expand Up @@ -185,6 +187,18 @@ Runnable.prototype.retries = function (n) {
this._retries = n;
};

/**
* Set or get number of repeats.
*
* @private
*/
Runnable.prototype.repeats = function (n) {
if (!arguments.length) {
return this._repeats;
}
this._repeats = n;
};

/**
* Set or get current retry
*
Expand All @@ -197,6 +211,18 @@ Runnable.prototype.currentRetry = function (n) {
this._currentRetry = n;
};

/**
* Set or get current repeat
*
* @private
*/
Runnable.prototype.currentRepeat = function (n) {
if (!arguments.length) {
return this._currentRepeat;
}
this._currentRepeat = n;
};

/**
* Return the full title generated by recursively concatenating the parent's
* full title.
Expand Down
12 changes: 12 additions & 0 deletions lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ var constants = utils.defineConstants(
* Emitted when {@link Test} execution has failed, but will retry
*/
EVENT_TEST_RETRY: "retry",
/**
* Emitted when {@link Test} execution has succeeded, but will repeat
*/
EVENT_TEST_REPEAT: "repeat",
/**
* Initial state of Runner
*/
Expand Down Expand Up @@ -867,6 +871,14 @@ Runner.prototype.runTests = function (suite, fn) {
self.fail(test, err);
}
self.emit(constants.EVENT_TEST_END, test);
return self.hookUp(HOOK_TYPE_AFTER_EACH, next);
} else if (test.currentRepeat() < test.repeats()) {
var repeatedTest = test.clone();
repeatedTest.currentRepeat(test.currentRepeat() + 1);
tests.unshift(repeatedTest);

self.emit(constants.EVENT_TEST_REPEAT, test, null);

return self.hookUp(HOOK_TYPE_AFTER_EACH, next);
}

Expand Down
21 changes: 21 additions & 0 deletions lib/suite.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ function Suite(title, parentContext, isRoot) {
this.root = isRoot === true;
this.pending = false;
this._retries = -1;
this._repeats = 1;
this._beforeEach = [];
this._beforeAll = [];
this._afterEach = [];
Expand Down Expand Up @@ -131,6 +132,7 @@ Suite.prototype.clone = function () {
suite.root = this.root;
suite.timeout(this.timeout());
suite.retries(this.retries());
suite.repeats(this.repeats());
suite.slow(this.slow());
suite.bail(this.bail());
return suite;
Expand Down Expand Up @@ -178,6 +180,22 @@ Suite.prototype.retries = function (n) {
return this;
};

/**
* Set or get number of times to repeat a passed test.
*
* @private
* @param {number|string} n
* @return {Suite|number} for chaining
*/
Suite.prototype.repeats = function (n) {
if (!arguments.length) {
return this._repeats;
}
debug('repeats %d', n);
this._repeats = parseInt(n, 10) || 0;
return this;
};

/**
* Set or get slow `ms` or short-hand such as "2s".
*
Expand Down Expand Up @@ -234,6 +252,7 @@ Suite.prototype._createHook = function (title, fn) {
hook.parent = this;
hook.timeout(this.timeout());
hook.retries(this.retries());
hook.repeats(this.repeats());
hook.slow(this.slow());
hook.ctx = this.ctx;
hook.file = this.file;
Expand Down Expand Up @@ -348,6 +367,7 @@ Suite.prototype.addSuite = function (suite) {
suite.root = false;
suite.timeout(this.timeout());
suite.retries(this.retries());
suite.repeats(this.repeats());
suite.slow(this.slow());
suite.bail(this.bail());
this.suites.push(suite);
Expand All @@ -366,6 +386,7 @@ Suite.prototype.addTest = function (test) {
test.parent = this;
test.timeout(this.timeout());
test.retries(this.retries());
test.repeats(this.repeats());
test.slow(this.slow());
test.ctx = this.ctx;
this.tests.push(test);
Expand Down
3 changes: 3 additions & 0 deletions lib/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ Test.prototype.clone = function () {
test.timeout(this.timeout());
test.slow(this.slow());
test.retries(this.retries());
test.repeats(this.repeats());
test.currentRetry(this.currentRetry());
test.currentRepeat(this.currentRepeat());
test.retriedTest(this.retriedTest() || this);
test.globals(this.globals());
test.parent = this.parent;
Expand All @@ -91,6 +93,7 @@ Test.prototype.clone = function () {
Test.prototype.serialize = function serialize() {
return {
$$currentRetry: this._currentRetry,
$$currentRepeat: this._currentRepeat,
$$fullTitle: this.fullTitle(),
$$isPending: Boolean(this.pending),
$$retriedTest: this._retriedTest || null,
Expand Down
3 changes: 3 additions & 0 deletions lib/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ export interface MochaOptions {
/** Number of times to retry failed tests. */
retries?: number;

/** Number of times to repeat each test. */
repeats?: number;

/** Slow threshold value, in milliseconds. */
slow?: number;

Expand Down
36 changes: 36 additions & 0 deletions test/assertions.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,42 @@ module.exports = {
});
},
)
.addAssertion(
"<JSONResult> [not] to have repeated test <string>",
(expect, result, title) => {
expect(result.tests, "[not] to have an item satisfying", {
title,
currentRepeat: expect.it("to be positive")
});
},
)
.addAssertion(
"<JSONResult> [not] to have repeated test <string> <number>",
(expect, result, title, count) => {
expect(result.tests, "[not] to have an item satisfying", {
title,
currentRepeat: count,
});
},
)
.addAssertion(
"<JSONResult> [not] to have repeated test <string>",
(expect, result, title) => {
expect(result.tests, "[not] to have an item satisfying", {
title,
currentRepeat: expect.it("to be positive"),
});
}
)
.addAssertion(
"<JSONResult> [not] to have repeated test <string> <number>",
(expect, result, title, count) => {
expect(result.tests, "[not] to have an item satisfying", {
title,
currentRepeat: count,
});
}
)
.addAssertion(
"<JSONResult> [not] to have failed with (error|errors) <any+>",
function (expect, result, ...errors) {
Expand Down
Loading