如何等待“/_up”返回 200 后再跑 Jest 测试?

解读

在 CI/CD 或本地开发流水线里,CouchDB 容器启动后到真正可用存在“时滞”:端口已监听但内部 Erlang 节点尚未完成初始化,此时直接跑 Jest 会导致随机失败。面试官想确认候选人是否理解“可用性探测”与“测试生命周期钩子”这两个关键环节,以及能否给出可落地、可移植、不依赖额外工具的国内工程化方案。

知识点

  1. /_up 语义:CouchDB 内置的健康端点,返回 {"status":"ok"} 且 HTTP 200 即表示集群已可读写;若返回 503 则仍在预热。
  2. Jest 生命周期globalSetup 运行于所有 test suite 之前,一旦抛出异常整个测试进程退出,天然适合做阻塞式就绪检查。
  3. 无外部依赖重试:使用 Node 原生 http + setTimeout 轮询,避免安装 wait-on、dockerize 等额外包,符合国内“离线内网/私有源”场景。
  4. 超时与熔断:必须设置硬超时上限(如 60 s),防止 CI 任务无限挂起;同时打印诊断信息方便运维排障。
  5. 环境变量注入:通过 COUCH_URLCOUCH_UP_TIMEOUT 实现“一份代码,多处部署”,兼容开发机、GitLab-Runner、阿里/腾讯/华为云容器服务。

答案

  1. 在项目根新建 test/setup/waitCouchUp.js
const http = require('http');
const COUCH_URL = process.env.COUCH_URL || 'http://127.0.0.1:5984';
const TIMEOUT = Number(process.env.COUCH_UP_TIMEOUT) || 60_000;

function checkOnce() {
  return new Promise((resolve, reject) => {
    const req = http.get(`${COUCH_URL}/_up`, { timeout: 3000 }, (res) => {
      let body = '';
      res.on('data', chunk => body += chunk);
      res.on('end', () => {
        if (res.statusCode === 200 && JSON.parse(body).status === 'ok') {
          return resolve();
        }
        reject(new Error('CouchDB not ready'));
      });
    });
    req.on('error', reject);
    req.on('timeout', () => req.destroy(new Error('Request timeout')));
  });
}

module.exports = async () => {
  const started = Date.now();
  while (true) {
    try {
      await checkOnce();
      console.log('[Jest globalSetup] CouchDB is up.');
      return;
    } catch (e) {
      if (Date.now() - started > TIMEOUT) {
        throw new Error(`CouchDB /_up did not return 200 within ${TIMEOUT} ms`);
      }
      await new Promise(r => setTimeout(r, 500));
    }
  }
};
  1. jest.config.js 中注册:
module.exports = {
  globalSetup: '<rootDir>/test/setup/waitCouchUp.js',
  testEnvironment: 'node',
};
  1. 本地或 CI 启动 CouchDB 后运行 npm test 即可;Jest 会阻塞到 /_up 返回 200 才开始执行任何用例

拓展思考

  • 多节点集群:若面试场景为“三节点 CouchDB 集群”,可把 /_up 换成 /_membership 并校验 cluster_nodesall_nodes 一致,再跑测试,展示对分布式一致性的理解。
  • 并行流水线:在 GitHub Actions 或 GitLab CI 中,把 wait 脚本做成独立 job,通过 artifacts:reports:dotenv 把可用标志传递给后续 job,实现“启动与测试阶段解耦”,提升流水线并发度。
  • SDK 内置重试:生产代码使用 nanopouchdb-adapter-http 时,配合 exponential backoff 策略,即使测试阶段未等待,也能在运行时自愈,体现“防御式编程”思维。