Skip to content
Latchkey

Node.js process.exit() in Tests - Lost Output & False Passes

Calling process.exit() terminates Node immediately. Inside a test (or app code the test imports), it tears the process down before buffered stdout flushes and before remaining assertions run - producing truncated logs or a misleading exit code.

What this error means

Test output cuts off mid-run, a reporter shows fewer tests than expected, or a suite "passes" because a forced exit code 0 fired before a failing assertion. Removing the process.exit() makes the real result appear.

Test output
# expected 40 tests, log stops after 12 with no summary
PASS  src/a.test.ts
PASS  src/b.test.ts
... (output truncated - process exited)

Common causes

App code calls process.exit() when imported by a test

A module that calls process.exit() at load or in a code path the test exercises kills the whole test process, not just that module.

A test handler forces an exit

An afterAll/teardown or a stray process.exit(0) ends the run early, so later tests never execute and buffered output is lost.

How to fix it

Set exitCode instead of calling exit

Setting process.exitCode lets Node finish flushing and run remaining work, then exits with your code naturally.

app code
// instead of: process.exit(1)
process.exitCode = 1;
// let the event loop drain

Guard CLI-only exits

Only exit when the module is the entry point, so importing it from a test does not terminate the runner.

cli.mjs
import { fileURLToPath } from 'node:url';
if (process.argv[1] === fileURLToPath(import.meta.url)) {
  main().catch((e) => { console.error(e); process.exitCode = 1; });
}

How to prevent it

  • Prefer process.exitCode = n over process.exit(n) so output flushes.
  • Guard process exits behind an "is this the entry point" check.
  • Lint for process.exit in test files.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →