## TL;DR

Mocha's default 2s timeout applies to hooks too, and an afterEach doing real async cleanup (closing DB connections, shutting down a server, flushing queues) can blow past it. Await the cleanup properly, make it faster, or raise the timeout on that hook.

## Error

```text
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
    at Hook.[anonymous] (afterEach hook)
```

## Steps

1. Read the stack and confirm the timeout is on the afterEach hook, not on a test. Expected: the failing hook is named in the output.
2. Look at what the hook awaits (db close, server shutdown, file cleanup); add timing logs if it is unclear. Expected: you know which call is slow.
3. Make sure the hook returns the promise or awaits it; a floating promise makes Mocha wait out the full timeout and then fail. Expected: the hook function is async and every cleanup call is awaited.
4. If cleanup is legitimately slow, raise the timeout on that hook with `this.timeout(10000)` inside a `function ()` hook (not an arrow function). Expected: the hook gets 10 seconds.
5. Rerun the file. Expected: the hook completes and the suite passes.

## When to use

- The error names afterEach and cites 2000ms.
- Tests pass but the run fails during teardown.
- The afterEach hook closes real resources (database, server, browser).

## When not to use

- The timeout is inside a test body; fix the test or the code under test.
- beforeEach times out; the setup is slow, which is a different hook.
- The hook throws immediately; that is an error, not a timeout.

## Tool compatibility

- Mocha 8 through 11.
- Applies to before and after hooks as well; the `--timeout` CLI flag sets the suite-wide default.

## Variant phrasings

### Timeout of 2000ms exceeded, with "(afterEach hook)" in the stack

The hook attribution tells you teardown is the problem, not any test.

### ensure "done()" is called, when mixing callbacks and promises

You mixed a done callback with a returned promise. Pick one style; returning the promise is the cleaner fix.

## Why it happens

Hooks share the test timeout budget. Async teardown against real infrastructure routinely exceeds 2 seconds, and an un-awaited promise makes Mocha sit through the whole timeout before failing instead of waiting on the work.

## Edge cases

- Arrow functions have no mocha `this`, so `this.timeout` inside one silently does nothing; use `function`.
- A hook that never resolves (a hanging close) needs fixing, not a bigger timeout.
- `--exit` does not help here; the hook still runs before the process exits.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_WdaZ98xVvw0LHiEURXVQgg
