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

使用Vitest测试集成@fastify/websocket的Fastify后端时,测试执行完成却仍超时

使用Vitest测试集成@fastify/websocket的Fastify后端时,测试执行完成却仍超时

看起来你遇到的是典型的测试资源未及时清理导致Vitest误判测试上下文未结束的问题——虽然你的测试代码逻辑上已经跑完全部断言,但事件循环里还残留着未彻底释放的活跃句柄(比如半关闭的WebSocket连接、Fastify实例资源),导致Vitest一直在等事件循环清空,最后触发超时。结合你的代码和依赖版本,我给你几个排查和解决的方向:


1. 调整afterEach的资源清理顺序

你现在的afterEach是先关闭Fastify实例,再终止WebSocket连接,这个顺序可能搞反了:Fastify关闭时会尝试优雅关闭所有WebSocket连接,但此时你的测试用ws客户端还处于半活跃状态,反而会拖慢清理速度,甚至残留句柄。

把顺序反过来,先强制终止WebSocket连接,再关闭Fastify实例:

afterEach(async () => {
  // 先切断客户端WebSocket连接,避免Fastify关闭时等待连接优雅断开
  ws?.terminate();
  // 再关闭Fastify实例,释放所有服务器端资源
  await app.close();
});

2. 强化Fastify实例的WebSocket清理逻辑

Fastify 5.x和@fastify/websocket 11.x的组合下,服务器端处理错误关闭后,可能还有未清理的socket句柄。你可以在构建Fastify实例时,显式配置websocket插件的清理选项,或者添加钩子确保所有连接被销毁:

// 假设你的buildApp函数是创建Fastify实例的地方
async function buildApp() {
  const app = fastify();
  app.register(require('@fastify/websocket'), {
    // 确保服务器端在关闭时强制清理所有websocket连接
    closeOnTrailers: true
  });
  // 额外添加钩子,手动清理所有活跃的websocket连接
  app.addHook('onClose', async (instance) => {
    const websocketServer = instance.websocketServer;
    if (websocketServer) {
      websocketServer.clients.forEach(client => {
        if (client.readyState !== WebSocket.CLOSED) {
          client.terminate();
        }
      });
    }
  });
  // 其他路由和配置...
  return app;
}

3. 给测试Promise加兜底,避免无限等待

虽然你用了ws.once('close')(触发后会自动移除监听器),但可以给测试的Promise加一个超时兜底,同时手动做监听器清理的保险:

it('disconnects if the client sends malformed JSON', async () => {
  const { promise, resolve } = Promise.withResolvers<CloseMessage>();
  const closeHandler = (code, reason) => {
    resolve({ code, reason });
  };
  ws.once('close', closeHandler);

  // 给测试Promise加兜底超时,避免因close事件未触发导致的无限等待
  const timeoutPromise = new Promise((_, reject) => 
    setTimeout(() => reject(new Error('Close event timeout')), 5000)
  );
  const racePromise = Promise.race([promise, timeoutPromise]);

  ws.send('fgsfds'); // 发送无效JSON
  const { code } = await racePromise;
  
  // 你的断言逻辑...
  console.log("tests are done");

  // 手动移除监听器(虽然once会自动移除,但保险起见)
  ws.off('close', closeHandler);
}, 10000); // 给整个测试设置更长的超时时间,避免被Vitest默认超时打断

4. 触发事件循环收尾,确保无残留任务

Node 22.x的事件循环检测比旧版本更严格,Vitest 3.x会等待事件循环完全清空才会标记测试结束。你可以在测试最后加一行,让Node.js处理完所有待处理任务:

// 在所有断言之后添加
await new Promise(resolve => setImmediate(resolve));

这会让Node.js处理完所有微任务和宏任务,确保没有残留的活跃句柄。


另外,你提到console.log的内容被归因到下一个测试,这也侧面证明了测试之间的隔离性出了问题——前一个测试的WebSocket连接或Fastify实例没清理干净,导致下一个测试的上下文被污染。调整afterEach的清理顺序应该能同步解决这个问题。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 07:02:58