Mocha --parallel: test files collide on shared ports and database state when run across workers
0 reputation · 20 Jun 2026, 22:05 UTC
0 reputation · 20 Jun 2026, 22:05 UTC
Mocha's --parallel mode distributes test files across a pool of worker processes, each loading its files in isolation. That isolation ends at the process boundary: any external resource a suite touches — a TCP port, a database schema, a temp directory — is still shared by every worker on the machine.
The design goal here is to keep an existing suite green under --parallel without rewriting tests, where several files currently assume exclusive access to a fixed port and a single seeded database. Root-level hooks run once per worker process, so setup that was safe in serial mode now executes concurrently, and Mocha itself provides no built-in coordination primitive for namespacing resources between workers.
The uncertainty is which isolation strategy fits Mocha's scheduling model: deriving per-worker namespaces from something stable like the worker's process ID or an environment variable Mocha exposes, versus partitioning resources statically per test file, versus simply accepting serial mode for the coupled subset. Buffered per-file output also makes contention failures harder to attribute.
Assuming a recent Mocha major version (to be confirmed with npx mocha --version):
--parallel (reporters, exclusive tests) that should rule it out for suites with shared fixtures?35275 reputation · 20 Jun 2026, 22:33 UTC
Yes, this is solvable without rewriting tests. Mocha's parallel mode assigns whole test files to separate Node worker processes, and inside each worker Mocha sets a MOCHA_WORKER_ID environment variable. Your root-level hooks can read it to derive a per-worker port and database name, so each worker gets its own namespace and the collisions disappear. For the subset of files that genuinely cannot be isolated, there is no supported "run this file serially" flag — the practical answer is two Mocha invocations, one parallel and one serial.
Under --parallel, each worker is a full Node process that loads its assigned files plus your root hooks (mochaHooks). Setup that ran exactly once in serial mode now runs once per worker, concurrently. The process boundary isolates memory, but not TCP ports, database schemas, or temp directories — those are machine-wide, so N workers all binding port 3000 or seeding the same test database will collide. Mocha deliberately ships no coordination primitive; namespacing is your job.
The standard pattern is a global root hook that derives everything from the worker id:
// test/hooks.js
exports.mochaHooks = {
async beforeAll() {
const workerId = process.env.MOCHA_WORKER_ID || '0';
process.env.APP_PORT = String(3000 + Number(workerId));
process.env.DB_NAME = `testdb_${workerId}`;
await createDatabase(process.env.DB_NAME); // create + migrate + seed
await startApp(Number(process.env.APP_PORT));
},
async afterAll() {
await stopApp();
await dropDatabase(process.env.DB_NAME);
}
};Prefer MOCHA_WORKER_ID over process.pid: the pid is unique but unstable across runs, which makes debugging and cleaning up leftover databases harder. A small integer id also keeps port arithmetic predictable.
Verify before relying on it. The variable's presence and semantics can vary across Mocha majors, so confirm against your installed version rather than assuming:
npx mocha --version
# temporary hook: log process.env.MOCHA_WORKER_ID and process.pid per workerIf it is absent in your version, fall back to static per-file partitioning (each file owns a fixed port/schema). That is deterministic and version-independent, but wastes resources and needs rebalancing when files are added or removed.
There is no supported way to pin individual files to serial execution inside a parallel run. The accepted workaround is splitting the suite:
"scripts": {
"test:parallel": "mocha --parallel 'test/**/*.spec.js' --ignore 'test/coupled/**'",
"test:serial": "mocha 'test/coupled/**/*.spec.js'",
"test": "npm run test:parallel && npm run test:serial"
}Note the trade-off: two invocations change CI timing and, if you merge coverage or JUnit reports, you must combine them yourself.
.only is disallowed in parallel mode.MOCHA_WORKER_ID from a root hook and confirm distinct values across workers.--parallel several times with derived ports/schemas; expect zero EADDRINUSE errors and no cross-worker fixture corruption.testdb_* databases after the run.One caveat: this assumes a recent Mocha major where --parallel is stable. Confirm behavior against your installed release notes before baking the worker-id assumption into CI.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.