Mocha’s Built‑In Retry Feature: When and How to Use it for Flaky Tests
Mocha’s built‑in retry feature lets you re‑run flaky tests up to a set limit. Learn how to use <code>this.retries(n)</code>, see a real example, and understand when retries help or hurt your test suite.
08 Oct 2025, 12:36 UTC

Why Flaky Tests Are a Pain and How Mocha Helps
In a continuous‑integration pipeline, a single flaky test can mask real regressions, waste minutes of build time, and erode confidence in the test suite. The classic symptom is a test that sometimes passes, sometimes fails, with no obvious code change. Rather than chasing hidden race conditions or external service hiccups, many teams turn to test‑retry mechanisms. Mocha, one of the most popular test runners for Node.js, ships with a lightweight retry API that lets you declare how many times a test should be re‑executed before it is finally marked as failed.
Mocha’s Retry API in a Nutshell
The core idea is simple: inside a test (or a hook) call this.retries(n) where n is the maximum number of retries. Mocha will automatically re‑run the test up to n times if it fails. Each retry runs the full test lifecycle – beforeEach and afterEach are executed again, but the this context is preserved, so you can share data between attempts.
Key points:
- Scope:
this.retries()can be called inside a test or a hook. Calling it in abeforeEachapplies to all subsequent tests in the suite. - Reporting: The spec reporter shows the attempt number, e.g.
1/2for the second attempt. - Compatibility: Works with callback, promise, and async/await tests.
- CLI support: Some Mocha releases expose a
--retriesflag for a global default.
Concrete Example: A Flaky API Test
Suppose we test an HTTP endpoint that occasionally returns a 502 due to a downstream service hiccup. We want to give it a chance to recover before failing the build.
const assert = require('assert');
const axios = require('axios');
describe('GET /status', function () {
// Allow up to 3 retries for this suite
this.retries(3);
it('should return 200 and a healthy status', async function () {
// Each retry gets fresh beforeEach/afterEach execution
const response = await axios.get('https://api.example.com/status');
assert.strictEqual(response.status, 200);
assert.strictEqual(response.data.status, 'healthy');
});
});
Running npx mocha --reporter spec will produce output like:
GET /status
✓ should return 200 and a healthy status (1/3)
1 passing (1200ms)
If the test fails on the first attempt, Mocha will retry up to three times. If it still fails after the third attempt, the exit code remains non‑zero, so CI will still mark the build as failed.
Trade‑Offs and Caveats
- Masking Issues: Retries can hide real problems. A test that passes after a retry might still be flaky in production.
- Longer Feedback Loop: Each retry adds to the total runtime. In a large suite, a high retry count can noticeably slow down CI.
- State Leakage: Because
beforeEachruns again, shared state should be reset or avoided. Side effects that persist across retries can lead to false positives. - Timeouts: Retries are subject to Mocha’s
--timeoutsetting. A test that hangs will be retried until the timeout is reached each time.
When to Use Retries Wisely
Use retries only for tests that are inherently non‑deterministic but otherwise correct:
- External network calls that can transiently fail.
- Tests that depend on a third‑party service with rate limits or known instability.
- UI tests that involve animations or timing issues on flaky browsers.
Never use retries to cover:
- Shared global state that isn’t cleaned up.
- Race conditions in your code under test.
- Unreliable test data or mocks.
Actionable Checklist for Adding Retries
- Identify flaky tests – run
npx mocha --reporter speclocally and look for intermittent failures. - Confirm Mocha version supports
this.retries()–npx mocha --versionshould show ≥ 7.0.0 (the feature was stable from 7.x). - Set a conservative retry count – start with 1 or 2, not 10.
- Run the suite in CI – ensure the exit code is still non‑zero when a test fails after exhausting retries.
- Document the reason – add a comment in the test explaining why a retry is needed.
- Optional: add
--retriesflag for a global default if many tests share the same pattern.
By following this checklist, you can reduce noise from flaky tests while keeping your CI fast and reliable.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.