如何等待“/_up”返回 200 后再跑 Jest 测试?
解读
在 CI/CD 或本地开发流水线里,CouchDB 容器启动后到真正可用存在“时滞”:端口已监听但内部 Erlang 节点尚未完成初始化,此时直接跑 Jest 会导致随机失败。面试官想确认候选人是否理解“可用性探测”与“测试生命周期钩子”这两个关键环节,以及能否给出可落地、可移植、不依赖额外工具的国内工程化方案。
知识点
- /_up 语义:CouchDB 内置的健康端点,返回
{"status":"ok"}且 HTTP 200 即表示集群已可读写;若返回 503 则仍在预热。 - Jest 生命周期:
globalSetup运行于所有 test suite 之前,一旦抛出异常整个测试进程退出,天然适合做阻塞式就绪检查。 - 无外部依赖重试:使用 Node 原生
http+setTimeout轮询,避免安装 wait-on、dockerize 等额外包,符合国内“离线内网/私有源”场景。 - 超时与熔断:必须设置硬超时上限(如 60 s),防止 CI 任务无限挂起;同时打印诊断信息方便运维排障。
- 环境变量注入:通过
COUCH_URL与COUCH_UP_TIMEOUT实现“一份代码,多处部署”,兼容开发机、GitLab-Runner、阿里/腾讯/华为云容器服务。
答案
- 在项目根新建
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));
}
}
};
- 在
jest.config.js中注册:
module.exports = {
globalSetup: '<rootDir>/test/setup/waitCouchUp.js',
testEnvironment: 'node',
};
- 本地或 CI 启动 CouchDB 后运行
npm test即可;Jest 会阻塞到 /_up 返回 200 才开始执行任何用例。
拓展思考
- 多节点集群:若面试场景为“三节点 CouchDB 集群”,可把
/_up换成/_membership并校验cluster_nodes与all_nodes一致,再跑测试,展示对分布式一致性的理解。 - 并行流水线:在 GitHub Actions 或 GitLab CI 中,把 wait 脚本做成独立 job,通过
artifacts:reports:dotenv把可用标志传递给后续 job,实现“启动与测试阶段解耦”,提升流水线并发度。 - SDK 内置重试:生产代码使用
nano或pouchdb-adapter-http时,配合 exponential backoff 策略,即使测试阶段未等待,也能在运行时自愈,体现“防御式编程”思维。