mocha "Error: Timeout of 2000ms exceeded" only when the full suite runs in CI: how to find the slow hook
Finds the slow hook behind mocha's 'Timeout of 2000ms exceeded' errors that appear only when the full suite runs in CI. Shows how to get per-test timing from the spec reporter, identify hooks eating the timeout budget, and bisect by file to isolate cross-test interference. Use when a mocha test times out in the full CI run but passes alone. Not for genuinely slow tests, async hangs with no resolution, or non-mocha runners.
TL;DR
The timed-out test is usually innocent: a before or beforeEach hook (or an earlier test in the same file) eats most of the 2-second budget, and the named test just happens to be holding the bag when the timer fires. Run the full suite with the spec reporter to see per-test durations, find the hook or neighbor that is slow, and fix that instead of raising the timeout. The timeout is a messenger, not the problem.
mocha "Error: Timeout of 2000ms exceeded" only when the full suite runs in CI: how to find the slow hookSteps
- Run the full suite the way CI does, with the spec reporter:
npx mocha --reporter spec. The reporter prints each test's duration in milliseconds. Expected: you get a full timing map and can see which tests and files are slow. - Look at the timed-out test's neighbors: a slow test just before it, or a
before/beforeEachhook in the same file that does heavy setup (DB seeding, server boot, big fixture load). Expected: you identify a hook or sibling test consuming most of the budget. - Confirm by running the single file alone, then the single test alone with
--grep. Expected: the test passes fast alone and slow (or timing out) in the file, which proves the cost comes from shared setup, not the test. - Fix the slow part: move one-time setup from
beforeEachtobefore, shrink the seeded data, or split the heavy file so the hook cost is not shared by every test. Expected: the hook duration drops and the timeout disappears in the full run. - Rerun the full suite in CI. Expected: no timeout errors and the total suite time is lower.
Use this when
- Mocha reports "Timeout of 2000ms exceeded" only in the full CI run
- The same test passes quickly when run alone with --grep
- You suspect hooks or neighboring tests, not the failing test itself
Not for this skill when
- The test hangs forever even alone (that is an unresolved promise or missing done, not a slow hook)
- The test is genuinely slow and needs a longer timeout on its own merits
- The runner is jest or vitest, which attribute timeouts differently
Variant phrasings
- mocha timeout only when running full suite
- mocha 2000ms exceeded in CI but passes locally alone
- find slow mocha hook causing timeout
Why it happens
Mocha's timeout covers the test plus its hooks: a beforeEach that takes 1.9 seconds leaves 100ms for the test itself. In a full run, hooks accumulate shared state and contention (a database filling up, a port pool draining), so setup that is fast in isolation gets slow at suite scale. The timeout names the test, but the budget was spent before the test body ran.
Edge cases
- Root-level hooks (in a root hook plugin or a top-level beforeEach) affect every file; check those when many files time out at once.
--parallelmode changes which tests share a process; a timeout that appears only in parallel mode is worker contention, not a slow hook.- Do not raise the timeout globally to silence this. It hides the slow setup and makes the whole suite slower for everyone.
Provenance
Resolved from the public thread: https://vectle.com/posts/psttiiE1h9KBL4EYKHt7OwFA