# Comparison

> Graceful shutdown testing tools compared: shutdown-check next to terminus, http-terminator, unit tests, kill-and-curl scripts and staging-only checks.

Source: https://shutdown.jscrate.dev/docs/comparison
Last updated: 2026-09-23

Shutdown libraries and shutdown tests solve different problems. A library
adds signal handling and cleanup to your service. shutdown-check starts the
real service and tests whether that behavior works from the outside.

## What is shutdown-check for?

Use shutdown-check when you already have a signal handler—written by you, your
framework, or a library—and want to verify the complete process lifecycle.

It checks:

- the production-like start command;
- readiness;
- a real active HTTP request;
- `SIGTERM` delivery;
- response completion;
- optional traffic draining;
- process exit and exit code;
- final port closure.

It does not install a signal handler or close application resources for you.

## Shutdown libraries

Libraries such as `@godaddy/terminus`, `http-terminator`, `lightship`, and
`close-with-grace` run inside the application. They may register signal
listeners, close HTTP servers, manage timeouts, and provide cleanup hooks.

Use a library when you need an implementation. Then test the configured result
with shutdown-check. A correct library can still be used incorrectly: the
wrong server object may be closed, cleanup may run in the wrong order, or the
production command may prevent signals from reaching Node.js.

## Framework shutdown hooks

Fastify, NestJS, and other frameworks provide shutdown APIs. These are also
implementations, not end-to-end tests.

Framework tests often call a close method directly. A deployment sends a
signal to a process. shutdown-check covers the signal listener, command,
framework, open socket, and final exit together.

## Unit tests

A unit test can call the shutdown function and assert that mocked dependencies
were closed.

Strengths:

- fast;
- precise failure location;
- easy to cover error branches;
- suitable for database and queue cleanup logic.

Limits:

- usually no real child process;
- no operating-system signal;
- mocked HTTP connections;
- no proof that the production start command exits;
- no check for orphaned child processes.

Keep unit tests. Add a black-box test for the process behavior they cannot
cover.

## Shell scripts with kill and curl

A custom script can start the app, use `curl`, send `kill -TERM`, and inspect
the exit.

This approach can work, but reliable synchronization is difficult. A fixed
sleep does not prove that a request was active when the signal arrived. The
script must also handle concurrent requests, response bodies, timeouts,
process groups, cleanup, exit codes, and CI reports.

shutdown-check packages those details into a repeatable config and stable
diagnostic codes.

## Staging and Kubernetes tests

A staging deployment is the only place to test cluster-level behavior such as
endpoint propagation, ingress timing, sidecars, and `preStop` hooks.

It is also slower and harder to reproduce. Requests may not be active during
termination, and a successful rollout does not prove that no request was
dropped.

Use shutdown-check before deployment for deterministic server behavior, then
use staging tests for infrastructure behavior.

## Comparison table

| Capability                              | shutdown-check | Shutdown library | Unit test  | Shell script | Staging test |
| --------------------------------------- | -------------- | ---------------- | ---------- | ------------ | ------------ |
| Adds shutdown behavior                  | No             | Yes              | No         | No           | No           |
| Runs the real start command             | Yes            | N/A              | Usually no | Yes          | Yes          |
| Sends a real `SIGTERM`                  | Yes            | Handles it       | Usually no | Yes          | Yes          |
| Proves work is active before the signal | Yes            | No               | With mocks | Hard         | Hard         |
| Verifies response status and body       | Yes            | No               | Yes        | Possible     | Possible     |
| Checks readiness withdrawal             | Optional       | May implement    | With mocks | Possible     | Yes          |
| Checks process exit and final port      | Yes            | No               | No         | Possible     | Indirect     |
| Checks databases and queues directly    | No             | May close them   | Yes        | Custom       | Custom       |
| Covers cluster routing and sidecars     | No             | No               | No         | No           | Yes          |
| Produces stable failure codes           | Yes            | No               | Test names | Custom       | Custom       |

## Which approach should I use?

For most services:

1. Use framework features, a library, or a small handler to implement shutdown.
2. Unit-test application-specific cleanup and error paths.
3. Run shutdown-check locally and in CI for the process and HTTP lifecycle.
4. Test Kubernetes, proxy, and load-balancer behavior in staging.

These layers complement each other. Replacing all of them with one test leaves
important behavior unverified.

## When shutdown-check is not enough

Add another test when correctness depends on:

- a database transaction after the HTTP response;
- queue acknowledgement;
- background jobs;
- WebSockets or HTTP/2;
- remote service coordination;
- container or cluster termination order.

The [compatibility page](https://shutdown.jscrate.dev/docs/compatibility) describes the boundary in more
detail.

## Related

- [How shutdown-check works](https://shutdown.jscrate.dev/docs/how-it-works)
- [Graceful shutdown in Node.js](https://shutdown.jscrate.dev/docs/guides/graceful-shutdown-nodejs)
- [Kubernetes and containers](https://shutdown.jscrate.dev/docs/guides/kubernetes)
- [Compatibility and limits](https://shutdown.jscrate.dev/docs/compatibility)
- [Run in CI](https://shutdown.jscrate.dev/docs/ci)
