为何将listen()移入test.before钩子后测试套件会永久阻塞?
Node.js测试套件移入test.before后永久阻塞的原因
现象说明
下面这段测试代码可以正常运行:
import assert from 'node:assert/strict'; import http from 'node:http'; import test from 'node:test'; test.describe(() => { const server = http.createServer((request, response) => { response.statusCode = 200; response.setHeader('Content-Type', 'text/plain'); response.setHeader('Content-Length', '12'); response.end('hello, world'); }); server.listen(8000, 'localhost'); test.after(() => { server.closeIdleConnections(); server.close(); }); test.test(async () => { const response = await fetch('http://localhost:8000/'); assert.equal(response.status, 200); assert.equal(response.headers.get('Content-Type'), 'text/plain'); assert.equal(response.headers.get('Content-Length'), '12'); assert.equal(await response.text(), 'hello, world'); }); });
但如果将server.listen()调用移入test.before钩子,测试套件就会永久阻塞:
import assert from 'node:assert/strict'; import http from 'node:http'; import test from 'node:test'; test.describe(() => { const server = http.createServer((request, response) => { response.statusCode = 200; response.setHeader('Content-Type', 'text/plain'); response.setHeader('Content-Length', '12'); response.end('hello, world'); }); test.before(() => { server.listen(8000, 'localhost'); }); test.after(() => { server.closeIdleConnections(); server.close(); }); test.test(async () => { const response = await fetch('http://localhost:8000/'); assert.equal(response.status, 200); assert.equal(response.headers.get('Content-Type'), 'text/plain'); assert.equal(response.headers.get('Content-Length'), '12'); assert.equal(await response.text(), 'hello, world'); }); });
原因分析
核心问题在于test.before的同步特性:它不会自动等待内部的异步操作完成。
server.listen()是异步调用,它会立即返回,实际完成端口监听的逻辑是在后续的事件循环中执行的。在第一个正常运行的代码里,listen()直接写在describe块中,虽然也是异步,但后续的测试用例是异步函数,会进入事件循环等待,等服务器完成监听后再执行fetch请求。
但移入test.before后,因为钩子是同步函数,Node.js测试框架会直接跳过等待,立刻执行后续的测试用例——这时候服务器大概率还没完成监听,fetch请求会因为连接不上而卡住,最终导致整个测试套件永久阻塞。
要解决这个问题,只需要把test.before改成异步钩子,明确等待server.listen()完成:
// 方式1:使用回调函数 test.before((done) => { server.listen(8000, 'localhost', done); }); // 方式2:用Promise包装成async钩子 test.before(async () => { await new Promise(resolve => server.listen(8000, 'localhost', resolve)); });
这样框架会等待服务器完成监听后再执行测试用例,就不会出现阻塞问题了。
内容的提问来源于stack exchange,提问作者Géry Ogam
相关产品推荐
相关产品推荐

