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

使用Vitest测试Express服务器时出现端口占用问题

解决Express服务器测试时端口占用问题

问题描述

实现了一个管理Express服务器启停的Server类,搭配Vitest测试用例,在beforeEach中启动服务器,afterEach中停止服务器。运行pnpm test时,频繁出现Error: listen EADDRINUSE: address already in use :::3000错误,导致测试用例执行失败,同时用Thunder Client发送请求也报错。已尝试为this.app.listen添加try/catch错误处理,但问题仅在测试场景下触发,未得到解决。

原因分析

  1. listen错误捕获失效:app.listen是异步方法,其错误通过error事件触发,而非抛出异常,因此原代码中的try/catch无法捕获端口占用错误。
  2. 服务器关闭不彻底:server.close()是异步操作,原测试用例的afterEach未等待关闭完成就执行下一个测试,导致端口未及时释放。
  3. 端口自增逻辑错误:this.port = this.port++写法存在问题,后置自增运算符会先返回原值再自增,最终端口号并未改变,无法实现端口重试的效果。

解决方案

1. 修复listen的错误处理逻辑

移除try/catch,改为监听服务器的error事件,处理端口占用时自动重试:

// 替换原init方法中的listen部分
this.server = this.app.listen(this.port, () => {
  console.info(`Listening on port ${this.port}`);
});

this.server.on('error', (err: any) => {
  if (err.code === 'EADDRINUSE') {
    console.info(`Port ${this.port} is already in use. Trying port ${this.port + 1}`);
    this.port++;
    // 重新启动服务器
    this.server.close();
    this.server = this.app.listen(this.port, () => {
      console.info(`Listening on port ${this.port}`);
    });
  } else {
    throw err;
  }
});

2. 让stop方法返回Promise,确保关闭完成

修改Server类的stop方法,封装异步关闭操作:

public stop(): Promise<void> {
  return new Promise((resolve, reject) => {
    this.server.close((err) => {
      if (err) {
        reject(err);
      } else {
        resolve();
      }
    });
  });
}

同时在测试用例的afterEach中等待关闭完成:

afterEach(async () => {
  await server.stop();
});

3. 修正端口自增逻辑

将原代码中的this.port = this.port++改为this.port++,确保端口号正确自增。

4. 可选:测试时使用动态分配端口

在Server构造函数中设置端口为0,让系统自动分配可用端口,从根源避免端口冲突:

constructor() {
  this.app = express();
  this.port = 0; // 0表示让系统分配随机可用端口
}

修改后,init方法中输出的端口号会是系统分配的实际端口,ngrok连接也会自动使用该端口。

内容的提问来源于stack exchange,提问作者Ethan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 08:22:31