# 在 node:test 或 Vitest 中测试

> 用 node:test 或 Vitest 测试优雅关闭：在测试中调用 checkShutdown，设置足够长的超时，断言结果，并在失败时打印时间线。

Source: https://shutdown.jscrate.dev/zh/docs/guides/test-runners
Last updated: 2026-09-23

如果希望优雅关闭（graceful shutdown）和其他测试一起运行，就用 `checkShutdown()`。这个函数会自己启动并清理服务，所以测试里不需要另外编写服务器的 setup 和 teardown。

## 用 node:test 测试

```js title="test/shutdown.test.mjs"
import assert from "node:assert/strict";
import { test } from "node:test";
import { checkShutdown, defineConfig } from "shutdown-check";

const config = defineConfig({
  command: ["node", "server.js"],
  env: { PORT: "3510" },
  baseUrl: "http://127.0.0.1:3510",
  readiness: { path: "/health", timeoutMs: 10_000 },
  workload: {
    path: "/slow",
    bodyIncludes: "work complete",
    started: { type: "response-headers" },
  },
  shutdown: {
    deadlineMs: 10_000,
    readinessWithdrawal: true,
  },
});

test("drains active requests on SIGTERM", { timeout: 30_000 }, async (t) => {
  const result = await checkShutdown(config);

  if (!result.pass) {
    for (const item of result.timeline) {
      const detail = item.detail ? ` — ${item.detail}` : "";
      t.diagnostic(`+${item.ms} ms ${item.event}${detail}`);
    }

    if (result.stderr) t.diagnostic(result.stderr);
    if (result.stdout) t.diagnostic(result.stdout);
  }

  assert.equal(result.pass, true, `${result.code}: ${result.message}`);
});
```

运行这个文件：

```sh
node --test test/shutdown.test.mjs
```

断言消息会把固定不变的诊断码和说明写进测试失败信息里。诊断信息（diagnostic）只在需要时才补充时间线和服务输出。

## 用 Vitest 测试

```ts title="test/shutdown.test.ts"
import { checkShutdown, defineConfig } from "shutdown-check";
import { expect, test } from "vitest";

const config = defineConfig({
  command: ["node", "server.js"],
  env: { PORT: "3511" },
  baseUrl: "http://127.0.0.1:3511",
  readiness: { path: "/health", timeoutMs: 10_000 },
  workload: {
    path: "/slow",
    bodyIncludes: "work complete",
    started: { type: "response-headers" },
  },
  shutdown: {
    deadlineMs: 10_000,
    readinessWithdrawal: true,
  },
});

test("drains active requests on SIGTERM", async () => {
  const result = await checkShutdown(config);

  if (!result.pass) {
    console.error(result.timeline);
    if (result.stderr) console.error(result.stderr);
    if (result.stdout) console.error(result.stdout);
  }

  expect(result.pass, `${result.code}: ${result.message}`).toBe(true);
}, 30_000);
```

运行：

```sh
npx vitest run test/shutdown.test.ts
```

Vitest 的默认超时通常太短，不够跑完一个真实进程的完整生命周期。请在测试上传入超时，或在 Vitest 配置中设置超时。

## 选择测试超时

外层测试的超时必须大于：

```text
readiness.timeoutMs
+ workload.started.timeoutMs
+ shutdown.deadlineMs
+ 进程本身的额外开销
```

使用默认值时，把测试运行器的超时设为 30 秒比较合理。这并不意味着每次运行都要 30 秒；正常情况下，服务一退出，运行就结束了。

如果测试运行器先超时，shutdown-check 的结果和时间线可能就看不到了。外层超时要给得宽松些，而关闭的截止时间要贴近生产环境的真实限制。

## 断言真正有用的值

即使检查发现了关闭问题（即预期内的失败），`checkShutdown()` 返回的 Promise 也会正常 resolve。所以要断言 `result.pass`，不能只看 Promise 有没有 resolve。

```js
assert.equal(result.pass, true, `${result.code}: ${result.message}`);
```

如果是反向测试，用来证明有问题的服务器会被检测出来，就断言诊断码：

```js
assert.equal(result.code, "SC311");
```

除非完整的文本本身就是你要测试的内容，否则不要匹配完整的消息。诊断码才是稳定的失败标识。

## 处理无效配置

输入无效时，`checkShutdown()` 会在检查开始前以一个 `Error` reject。

```ts
await assert.rejects(
  () => checkShutdown(invalidConfig),
  /baseUrl must use http:\/\//
);
```

`defineConfig()` 可以让编辑器给出提示，但要记住它不做运行时校验。校验是在调用 `checkShutdown()` 时进行的。

## 使用 CommonJS

```js title="test/shutdown.test.cjs"
const assert = require("node:assert/strict");
const { test } = require("node:test");
const { checkShutdown } = require("shutdown-check");

test("drains active requests", { timeout: 30_000 }, async () => {
  const result = await checkShutdown({
    command: ["node", "server.js"],
    env: { PORT: "3512" },
    baseUrl: "http://127.0.0.1:3512",
    readiness: { path: "/health" },
    workload: {
      path: "/slow",
      started: { type: "response-headers" },
    },
  });

  assert.equal(result.pass, true, `${result.code}: ${result.message}`);
});
```

shutdown-check 1.0.1 及以上版本支持 CommonJS。

## 给每个测试分配独立端口

测试运行器可能并行执行多个文件。每次检查都要求端口在启动前处于空闲状态，所以两个关闭检查不能共用同一个端口。

使用不同的端口：

```ts
const ordersConfig = defineConfig({
  env: { PORT: "3510" },
  baseUrl: "http://127.0.0.1:3510",
  // ...
});

const billingConfig = defineConfig({
  env: { PORT: "3511" },
  baseUrl: "http://127.0.0.1:3511",
  // ...
});
```

数据库记录、文件、队列和其他状态也要相互隔离。shutdown-check 运行的是真实的服务，发送的也是真实的请求。

## 可靠地解析路径

默认情况下，相对路径基于 `process.cwd()` 解析。测试运行器通常以项目根目录作为工作目录，但在 workspace 或自定义脚本中可能会变。

显式传入一个基准目录：

```js
const result = await checkShutdown(config, import.meta.dirname);
```

这样，相对的 `cwd` 值就会基于测试文件所在的目录解析。

## 避免测试失败后遗留进程

shutdown-check 在 resolve 之前，会清理它发出的测试请求、定时器和服务的进程组。不要在 `beforeAll` 钩子里再启动同一个服务器，进程的生命周期由检查自己管理。

如果测试运行器在 shutdown-check resolve 之前强制停止了测试，清理可能来不及完成。这也是外层超时要长于检查自身各项超时的另一个原因。

## 相关内容

- [Node API](https://shutdown.jscrate.dev/zh/docs/node-api)
- [配置参考](https://shutdown.jscrate.dev/zh/docs/configuration)
- [输出与报告](https://shutdown.jscrate.dev/zh/docs/output)
- [在 CI 中运行](https://shutdown.jscrate.dev/zh/docs/ci)
- [快速开始](https://shutdown.jscrate.dev/zh/docs/quick-start)
