Course contentsShow
← Node.js
Lesson 1582 of 1,98014. Testing Methodologies and Quality AssuranceFree lesson

Testing Graceful Shutdown Behavior

Verify that the server flushes pending transactions and closes connections before termination.

In this lesson, you will learn how to verify that your Node.js application cleanly flushes pending transactions and terminates active connections when receiving a termination signal.

When a process receives a SIGTERM or SIGINT, simply killing the process can lead to data corruption or orphaned database transactions. A robust "graceful shutdown" ensures the server stops accepting new requests, waits for in-flight requests to finish, and closes open handles (like DB pools) before exiting.

The Verification Strategy

To test this, we cannot rely on standard unit tests; we need an integration approach that simulates the signal. The goal is to prove that a transaction initiated just before the shutdown signal is fully persisted rather than dropped.

it('should finish pending transactions before closing', async () => {
  const server = startServer();
  
  // 1. Initiate a slow request that involves a DB transaction
  const pendingRequest = agent.post('/api/save-order').send({ id: 123 });
  
  // 2. Trigger the shutdown signal
  process.kill(process.pid, 'SIGTERM');
  
  // 3. Await the pending request and check if DB state is final
  await pendingRequest;
  const order = await db.orders.findById(123);
  
  expect(order.status).toBe('completed');
  // 4. Verify server is no longer listening
  await expect(server.listening).toBe(false);
});

Key Considerations

  1. The "Drain" Period: Your test must verify that the server does not immediately exit. If the test finishes before the transaction, you have a race condition. Use await on the request lifecycle.
  2. Connection Pools: If your database driver keeps a pool, your test must verify that pool.end() was called. You can mock the database adapter to spy on its end() or close() method to confirm the graceful shutdown logic invoked the teardown.
  3. Timeout Logic: Always ensure your test has a global timeout. If your shutdown logic hangs (e.g., waiting for a request that never ends), your test suite should fail rather than block indefinitely.

Testing this behavior ensures that your infrastructure doesn't treat an orchestrated deployment (like a Kubernetes rolling update) as a catastrophic crash.

Key Takeaway: Testing graceful shutdown requires verifying that the event loop remains active long enough to commit stateful operations before the process exits.