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

Fastify项目Jest+Supertest测试遇两问题:钩子报错与TCP句柄未关闭

Fastify + Jest + Supertest 测试问题解决指南

问题1:TypeError: Cannot read properties of undefined (reading 'length')

错误场景

测试健康检查接口时触发该错误,错误栈指向Fastify内部钩子preParsingHookRunner中的length属性读取操作。

排查与解决

该错误大概率和@fastify/csrf-protection插件的配置或路由校验规则有关:

  • 核心原因:Fastify v5中,csrf保护插件默认对所有路由启用校验,当请求未携带csrf token时,插件内部逻辑可能出现未定义值访问;同时插件依赖cookie插件的签名配置,缺失配置也会导致异常。
  • 解决方案:
    1. 给健康检查路由跳过csrf校验:在注册路由时添加配置,跳过该路由的csrf验证:
      // authRoutes示例代码
      import type { FastifyInstance } from 'fastify';
      
      export default async function authRoutes(fastify: FastifyInstance) {
        fastify.get('/healthcheck', { config: { csrf: false } }, async (req, reply) => {
          return reply.send({ status: 'ok' });
        });
      }
      
    2. 完善csrf插件配置:注册csrf插件时指定cookie相关配置,与cookie插件的签名规则匹配:
      // server.ts中修改csrf插件注册逻辑
      await fastify.register(cookiePlugin, {
        secret: process.env.COOKIE_SECRET || 'dev-secret', // 必须配置签名密钥
      });
      await fastify.register(csrfPlugin, {
        cookie: { signed: true },
      });
      
    3. 测试环境按需加载插件:如果测试阶段不需要csrf保护,可根据环境变量跳过插件加载:
      const buildServer = async () => {
        const fastify = Fastify({ logger: process.env.NODE_ENV !== 'test' });
      
        await fastify.register(sensiblePlugin);
        await fastify.register(cookiePlugin, { secret: 'test-secret' });
        // 非测试环境才加载csrf插件
        if (process.env.NODE_ENV !== 'test') {
          await fastify.register(csrfPlugin);
        }
        await fastify.register(helmetPlugin);
        await fastify.register(authRoutes, { prefix: '/api/auth' });
      
        return fastify;
      };
      

问题2:未关闭TCP句柄导致Jest超时退出

错误场景

调用app.close()后Jest仍无法正常退出,触发测试超时提示,后台检测到未关闭的TCPWRAP句柄。

排查与解决

核心问题是Fastify实例关闭不彻底,或异步操作未等待完成:

  • 核心原因:app.close()是异步方法,未等待其执行完成就结束测试;Fastify的日志组件或第三方插件可能持有未释放的TCP连接资源。
  • 解决方案:
    1. 等待close操作完成:在afterAll中必须使用await等待实例关闭:
      afterAll(async () => {
        await app.close();
      });
      
    2. 禁用测试环境日志:Fastify的日志组件可能持有TCP连接(如远程日志传输),测试时关闭日志可避免资源泄漏:
      // buildServer中初始化Fastify时关闭测试环境日志
      const fastify = Fastify({ logger: process.env.NODE_ENV !== 'test' });
      
    3. 增加超时时间:如果实例关闭需要更长时间,可给afterAll设置超时阈值:
      afterAll(async () => {
        await app.close();
      }, 10000); // 设置10秒超时
      
    4. 应急强制退出:如果以上方法无效,可在关闭实例后强制终止进程(仅应急使用):
      afterAll(async () => {
        await app.close();
        process.exit(0);
      });
      

完整测试文件示例

修改后的测试文件:

import supertest from 'supertest';
import buildServer from '../server';

describe('Server', () => {
  let app: Awaited<ReturnType<typeof buildServer>>;

  beforeAll(async () => {
    // 测试环境禁用日志
    app = await buildServer({ logger: false });
  });

  afterAll(async () => {
    await app.close();
  }, 10000);

  test('GET /api/auth/healthcheck', async () => {
    await supertest(app.server).get('/api/auth/healthcheck').expect(200);
  });
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 21:35:13