You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Jest全局配置等待docker compose up执行完成?

问题根源

你的代码存在两个核心问题,导致测试在依赖服务未就绪时就开始执行:

  1. exec返回的是ChildProcess对象而非Promise,await对它无效,Jest不会等待命令执行完成就进入测试阶段。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 12:53:33