Node.js — server.listen EADDRINUSE in tests or watch mode
Fix EADDRINUSE in tests and watch mode by releasing ports, using port 0, and closing servers after each test.
Symptoms
- Error: listen EADDRINUSE: address already in use :::3000
- Tests pass individually but fail when run together
- Watch mode restarts before the old server released the port
- A random port still collides occasionally
Possible causes
- A previous server instance still listens on the fixed port
- Tests start a server but never close it
- The test runner parallelizes tests that all bind the same port
- Watch mode restarts faster than the socket is released
Diagnosis steps
Find the lingering listener
ss -ltnp | grep :3000Interpretation
The process holding the port is often a leftover test server or an earlier watch process.
Use an ephemeral port in tests
const server = app.listen(0); const { port } = server.address()Interpretation
Binding to port 0 lets the OS pick a free port, avoiding collisions between parallel tests.
Close servers in teardown
afterAll(() => server.close())Interpretation
Releasing the listener after each suite frees the port for the next run.
Root cause
The port is already bound, either by a leftover server, by parallel tests sharing a fixed port, or by a previous instance not yet released during a restart.
Fix
Close servers in teardown, bind to port 0 in tests so the OS chooses a free port, and give watch mode time to release the socket or randomize its port.
Verify the fix
Run the full test suite and confirm no EADDRINUSE, and confirm the port is free after the run with a listener check.
Symptom walkthrough
Starting a server fails with EADDRINUSE, and tests that pass individually fail when run together. The pattern points at port reuse: a leftover server, parallel tests binding one fixed port, or watch mode restarting before the previous socket is released. Binding to a hardcoded port makes every run compete for the same resource.
Fix in practice
Close servers in teardown so ports are freed, bind to port 0 in tests and read the assigned port from the server address, and avoid fixed ports in watch mode. These changes let runs coexist without contending for one port.
Verification
Run the full suite and confirm no EADDRINUSE, then check that the port is free afterwards. Running clean together and leaving no listener behind confirms the fix. A fixture that both starts and stops the server also prevents future collisions between suites.
Diagnostic commands
Check the behavior with commands like these:
ss -ltnp | grep :3000
const server = app.listen(0); const { port } = server.address()
afterAll(() => server.close())