如何让Jest全局配置等待docker compose up执行完成?
问题根源
你的代码存在两个核心问题,导致测试在依赖服务未就绪时就开始执行:
exec返回的是ChildProcess对象而非Promise,await对它无效,Jest不会等待命令执行完成就进入测试阶段。docker compose up默认是前台运行,进程不会主动退出,on('exit')事件永远不会触发,setup逻辑直接跳过等待环节。
解决方案
步骤1:封装Promise化的命令执行函数
将exec包装为Promise,确保能正确等待命令执行完成,同时使用docker compose up -d让服务后台启动(此命令会在服务启动后正常退出)。
步骤2:等待依赖服务真正就绪
即使docker compose up -d执行完成,服务可能还在初始化(比如数据库需要加载数据),需添加等待逻辑,直到服务可用。
步骤3:配置全局Teardown清理服务
在测试结束后执行docker compose down,确保资源被正确回收。
修改后的global-setup.ts
/* eslint-disable */ import { exec } from 'child_process'; import { createConnection } from 'net'; import os from 'os'; // 封装Promise化的命令执行函数 const execCmd = async (cmd: string): Promise<string> => { return new Promise((resolve, reject) => { exec(cmd, (err, stdout, stderr) => { if (err) { console.error(`命令执行失败: ${err.message}`); console.error(stderr); reject(err); return; } console.log(stdout); resolve(stdout); }); }); }; // 等待指定TCP端口开放(替换为你的依赖服务端口,比如数据库的5432) const waitForPort = (port: number, host = 'localhost', timeout = 60000): Promise<void> => { const startTime = Date.now(); return new Promise((resolve, reject) => { const checkConnection = () => { if (Date.now() - startTime > timeout) { reject(new Error(`等待端口${port}超时`)); return; } const socket = createConnection({ host, port }, () => { socket.end(); resolve(); }); socket.on('error', () => setTimeout(checkConnection, 1000)); }; checkConnection(); }); }; module.exports = async function () { console.log('\nSetting up...\n'); // 后台启动docker服务 const dockerComposeUpCmd = 'docker compose up -d'; await execCmd(dockerComposeUpCmd); // 等待依赖服务就绪 console.log('等待依赖服务就绪...'); await waitForPort(5432); // 替换为你的服务端口 // 标记需要执行teardown globalThis.__NEED_DOCKER_TEARDOWN__ = true; console.log('Setup完成,开始执行测试...'); };
创建global-teardown.ts
/* eslint-disable */ import { exec } from 'child_process'; const execCmd = async (cmd: string): Promise<string> => { return new Promise((resolve, reject) => { exec(cmd, (err, stdout, stderr) => { if (err) { console.error(`Teardown命令执行失败: ${err.message}`); console.error(stderr); reject(err); return; } console.log(stdout); resolve(stdout); }); }); }; module.exports = async function () { if (globalThis.__NEED_DOCKER_TEARDOWN__) { console.log('\nTearing down...\n'); await execCmd('docker compose down -v'); // -v 会删除关联卷,避免残留数据 } };
更新Jest配置
在项目的jest.config.ts或project.json的E2E配置中,添加全局setup和teardown的路径:
export default { // 其他配置... globalSetup: '<rootDir>/global-setup.ts', globalTeardown: '<rootDir>/global-teardown.ts', };
可选:通过日志判断服务就绪
如果你的服务无法通过端口判断就绪状态(比如需要等待特定启动日志),可以替换waitForPort为以下函数:
// 等待指定服务的日志出现关键字 const waitForLog = (serviceName: string, keyword: string, timeout = 60000): Promise<void> => { const startTime = Date.now(); return new Promise((resolve, reject) => { const checkLogs = async () => { if (Date.now() - startTime > timeout) { reject(new Error(`等待服务${serviceName}日志超时`)); return; } try { const logs = await execCmd(`docker logs ${serviceName}`); if (logs.includes(keyword)) { resolve(); } else { setTimeout(checkLogs, 1000); } } catch (err) { setTimeout(checkLogs, 1000); } }; checkLogs(); }); }; // 在setup中使用示例(比如等待PostgreSQL就绪) await waitForLog('postgres-service', 'database system is ready to accept connections');
内容的提问来源于stack exchange,提问作者Arkadi
相关产品推荐
相关产品推荐

