如何在Jest+Supertest测试中正确关闭Polka服务器?
解决Polka中间件异常导致Jest无法退出及生产环境防护问题
问题根源
你的测试代码中,brokenMiddleware直接抛出未捕获的异常,Polka默认不会自动处理这类异常,导致请求连接挂起,Jest检测到未关闭的TCP句柄(TCPSERVERWRAP),最终无法正常退出。同时,这种未处理的异常在生产环境中会导致请求挂起甚至服务崩溃。
解决方案
1. 让Jest测试正常退出
核心是确保异常被捕获并正确终止请求响应,同时保证服务器关闭操作完成:
修改后的测试代码:
import polka, {Polka} from 'polka'; import { agent as request } from 'supertest'; function brokenMiddleware(req, res, next) { throw new Error("foobar"); } describe('integration', () => { describe('middleware', () => { let testPolka: Polka; beforeEach(() => { testPolka = polka() .use(brokenMiddleware) .get('/', async (req, res) => { res.end('Hello world'); }) // 添加全局错误处理,确保响应正常结束 .on('error', (err, req, res) => { if (!res.headersSent) { res.writeHead(500, { 'Content-Type': 'application/json' }); } res.end(JSON.stringify({ error: err.message })); }); }); // 异步关闭服务器,确保句柄完全释放 afterEach(async () => { if (testPolka?.server) { await new Promise<void>((resolve) => { testPolka.server.close(() => resolve()); }); } }); it('should handle middleware errors gracefully', async () => { const response = await request(testPolka.handler) .get('/') .set('Accept', 'application/json'); // 修正测试预期:中间件抛错应返回500状态 expect(response.status).toEqual(500); expect(response.headers["Content-Type"]).toMatch(/json/); }); }); });
关键改动:
- 给Polka实例绑定
error事件,捕获未处理的中间件异常,强制结束响应 - 将
afterEach改为异步,等待服务器关闭完成,确保TCP句柄释放 - 修正测试预期,匹配中间件异常后的500响应状态
2. 生产环境防护:避免异常影响服务
生产环境中必须通过两层防护避免中间件异常引发的问题:
(1)中间件自身捕获异常
不管是同步还是异步中间件,都要自行捕获异常并传递给错误处理流程:
// 同步中间件 function safeMiddleware(req, res, next) { try { // 原业务逻辑(可能抛错的代码) throw new Error("foobar"); } catch (err) { // 将错误传递给全局错误处理 next(err); } } // 异步中间件 async function asyncSafeMiddleware(req, res, next) { try { // 异步操作 await someAsyncOperation(); next(); } catch (err) { next(err); } }
(2)全局错误处理
给Polka实例绑定全局error事件,统一处理所有未捕获的错误,确保请求能正常终止:
const app = polka() .use(safeMiddleware) .get('/', (req, res) => { res.end('Hello world'); }) .on('error', (err, req, res) => { console.error('Server error:', err); // 确保响应未发送时返回500 if (!res.headersSent) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('Internal Server Error'); } else { // 已发送响应头时直接结束连接 res.end(); } }); app.listen(3000, () => console.log('Server running on port 3000'));
总结
- 测试环境:通过全局错误处理确保请求正常结束,异步关闭服务器释放句柄,让Jest能正常退出
- 生产环境:中间件自行捕获异常,配合全局错误处理,避免未捕获异常导致请求挂起或服务崩溃
内容的提问来源于stack exchange,提问作者Mikko Ohtamaa
相关产品推荐
相关产品推荐

