Shutdown Check

搜索文档

查找页面或章节

在 node:test 或 Vitest 中测试

在测试套件中运行 shutdown-check,失败时保留有用的输出。

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

用 node:test 测试

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}`);
});

运行这个文件:

node --test test/shutdown.test.mjs

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

用 Vitest 测试

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);

运行:

npx vitest run test/shutdown.test.ts

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

选择测试超时

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

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

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

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

断言真正有用的值

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

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

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

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

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

处理无效配置

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

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

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

使用 CommonJS

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。

给每个测试分配独立端口

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

使用不同的端口:

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 或自定义脚本中可能会变。

显式传入一个基准目录:

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

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

避免测试失败后遗留进程

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

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