Jest+Supertest测试Express时调用Auth0异步函数出现open handles无法正常退出
问题中两个TLSWRAP类型的open handle分别对应两个HTTPS请求的连接残留:一个是
getToken调用Auth0接口的HTTPS连接,另一个是supertest调用本地Express接口的HTTP/HTTPS连接,均由默认开启的TCP连接复用(keep-alive)未主动释放导致。
解决方案
1. 修复axios连接复用问题(优先解决Auth0请求导致的句柄)
getToken函数中axios默认开启了HTTP keep-alive,会复用HTTPS连接导致句柄残留,修改getToken的请求配置,显式禁用keep-alive:
import http from 'http' import https from 'https' import axios from 'axios' var options = { url: '你的Auth0接口地址', form: { // 原有表单配置不变 }, json: true }; const getToken = async () => { try { const tokenRequest = await axios.post(options.url, options.form, { // 新增配置,禁用连接复用 httpAgent: new http.Agent({ keepAlive: false }), httpsAgent: new https.Agent({ keepAlive: false }), }) return tokenRequest.data.access_token } catch (err){ return err } } export default getToken;
2. 修复mongoose连接的await遗漏
你当前beforeAll里的mongoose.connect没有加await,属于异步操作未等待完成就执行后续逻辑,可能导致数据库连接句柄残留,修改测试文件的beforeAll逻辑:
beforeAll(async() => { try { // 新增await等待数据库连接完成 await mongoose.connect(config.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, }); db = mongoose.connection; db.on("error", console.error.bind(console, "Console Error:")); db.once("open", () => console.log(`App connected to "${db.name}" database`) ); authToken = await getToken() } catch (err) { return err } });
3. 显式关闭Express服务(解决supertest请求的句柄残留)
第二个TLSWRAP是supertest调用本地接口时启动的HTTP服务没有主动关闭,修改测试文件,显式管理Express服务的生命周期:
import app from "../app"; import mongoose from "mongoose"; import supertest from "supertest"; import { getToken } from "../helpers"; import dotenv from "dotenv"; import * as config from "../config"; import http from 'http' dotenv.config(); const api = supertest(app); let authToken: any; let db: any; // 新增server变量存储Express服务实例 let server: http.Server; beforeAll(async() => { try { await mongoose.connect(config.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, }); db = mongoose.connection; db.on("error", console.error.bind(console, "Console Error:")); db.once("open", () => console.log(`App connected to "${db.name}" database`) ); authToken = await getToken() // 新增:启动Express服务,随机端口避免冲突 server = app.listen(0) } catch (err) { return err } }); describe("GET /interview/:idCandidate", () => { test("With auth0 and read permissions", async () => { await api .get("/interview/1") .set("Authorization", "Bearer " + authToken) .expect(200) }); }); afterAll(async () => { try { await db.close(); // 新增:关闭Express服务,等待关闭完成 await new Promise(resolve => server.close(resolve)) } catch (err) { return err; } });
4. 兜底方案:强制Jest退出
如果以上方案都不能解决,可在jest的启动命令中添加--forceExit参数,强制Jest在所有测试用例执行完成后直接退出,无需等待所有句柄释放:
jest --detectOpenHandles --forceExit
该方案适合端到端测试场景,不会影响测试结果的正确性。
内容的提问来源于stack exchange,提问作者Mauro Consolani
相关产品推荐
相关产品推荐

