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

Node.js实验性Testrunner启动Express服务器时冻结,如何解决?

Node v18实验性Testrunner与Express集成测试冻结问题排查

我正在使用Node v18的实验性Testrunner,将Express作为开发依赖用于HTTP集成测试,大部分测试正常,但有一个测试会导致Testrunner冻结/停止运行(无法继续)。我使用TS开发,但该问题在JS环境中也可复现,测试文件HttpTests.js内容如下:

import assert from 'assert/strict';
import express from 'express';
import test from 'node:test';

test('Http', async () => {
  const server = express();
    
  server.listen(3000);
  assert.ok(false);
});

通过npm脚本"test": "node --test $(find . -name '*Tests.js')"运行时,Testrunner会出现异常。请问问题出在哪里,或者缺少了什么配置?


为什么不使用默认的执行模型?

由于我使用TS,必须找到让ts-node与Testrunner配合的方法,目前我的TS项目使用特定npm脚本运行正常。


复现步骤

  • 我创建了包含TS和JS版本的最小复现仓库
  • 手动复现可执行:
    1. 执行命令:mkdir reproduction && cd reproduction && npm init -y && npm install express
    2. 创建test目录及其中的HttpTests.js文件,内容如上
    3. 修改package.json为:
      {
        "name": "reproduction",
        "type": "module",
        "scripts": {
          "test": "node --test $(find . -name '*Tests.js')"
        }
      }
      
    4. 运行测试脚本,Testrunner将无法正常结束。

已知Testrunner仍处于实验阶段

我清楚这一点,但项目中大部分测试运行正常,示例代码如下:

await t.test('subtest - saves data.', async () => {
    const expectedResult = {};
  
    const api = express();
    const port = await getRandomPort();
    const server = api
        .use(express.json())
        .post('/save', (request, response) => {
            response.json(expectedResult);
        })
        .listen(port);

    const httpDataProvider = new HttpDataProvider({ url: `http://localhost:${port}` });
    const actualResult = await httpDataProvider.saveSomething();

    assert.deepEqual(actualResult, expectedResult);

    server.close();
});

问题原因

核心问题是测试断言失败后,Express服务器未被关闭,导致Node.js进程因存在活跃的网络监听资源而无法退出。

在出问题的测试代码中,server.listen(3000)启动了服务器,但assert.ok(false)会直接抛出错误,后续关闭服务器的逻辑根本没机会执行。Node.js的Testrunner会等待所有异步资源(包括网络监听)释放后才会终止进程,因此服务器的监听进程会一直占用资源,导致Testrunner冻结。

解决方法

1. 用try/finally保证服务器必被关闭

无论测试成功还是失败,都要确保服务器被关闭,修改后的测试代码如下:

import assert from 'assert/strict';
import express from 'express';
import test from 'node:test';

test('Http', async () => {
  const server = express();
  server.listen(3000);
  try {
    assert.ok(false);
  } finally {
    // 无论断言是否失败,都会执行关闭逻辑
    server.close();
  }
});

2. 搭配随机端口使用(推荐)

像你正常运行的测试那样,使用随机端口启动服务器,既可以避免端口占用冲突,也能让测试更健壮:

import assert from 'assert/strict';
import express from 'express';
import test from 'node:test';
import { createServer } from 'node:net';

// 辅助函数:获取随机可用端口
async function getRandomPort() {
  return new Promise((resolve, reject) => {
    const server = createServer();
    server.listen(0, () => {
      const port = server.address().port;
      server.close(() => resolve(port));
    });
    server.on('error', reject);
  });
}

test('Http', async () => {
  const server = express();
  const port = await getRandomPort();
  server.listen(port);
  try {
    assert.ok(false);
  } finally {
    server.close();
  }
});

3. 可选:监听服务器启动事件

如果需要确保服务器完全启动后再执行测试逻辑,可以监听listening事件:

test('Http', async () => {
  const server = express();
  await new Promise(resolve => server.listen(3000, resolve));
  try {
    assert.ok(false);
  } finally {
    server.close();
  }
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 20:06:19