使用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错误处理,但问题仅在测试场景下触发,未得到解决。
原因分析
- listen错误捕获失效:
app.listen是异步方法,其错误通过error事件触发,而非抛出异常,因此原代码中的try/catch无法捕获端口占用错误。 - 服务器关闭不彻底:
server.close()是异步操作,原测试用例的afterEach未等待关闭完成就执行下一个测试,导致端口未及时释放。 - 端口自增逻辑错误:
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
相关产品推荐
相关产品推荐

