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

如何在Jest中测试无参数的Express错误处理中间件

为什么该中间件采用返回处理函数的形式

这是Express生态中非常通用的中间件工厂模式:

  • 常规直接接收error/req/res/next参数的是可直接注册的中间件,使用时写法为app.use(普通中间件)
  • 你提供的errorHandler本身不是直接给Express调用的中间件,而是用于生成中间件的工厂函数,使用时需要先执行拿到真正的处理逻辑,写法为app.use(errorHandler())

这种写法的核心价值是预留配置扩展能力,后续如果要给错误处理增加自定义逻辑(比如错误日志上报开关、自定义错误码映射、自定义响应格式),只需要给errorHandler增加入参即可,比如后续可扩展为errorHandler({ enableLog: true }),不需要改动中间件核心结构和注册逻辑。

Jest测试实现思路

测试逻辑和普通Express中间件没有本质区别,核心步骤只有两步:

  1. 先执行errorHandler(),拿到真正符合Express错误中间件签名的处理函数
  2. 构造mock的req/res/next对象,模拟不同的输入场景,断言中间件的行为是否符合预期

需要覆盖的核心测试场景:

  • 传入type === 'entity.too.large'的错误时,是否正确转换为携带PAYLOAD_TOO_LARGE错误码的错误返回
  • 当res.headersSent为true(响应已经发送)时,是否不会重复写入响应
  • 所有逻辑执行完成后,是否一定会调用next()移交控制权
  • 传入普通错误时,是否正常返回携带对应错误码的500响应
具体测试代码示例

注意mockres.status时需要支持链式调用,因为Express的res.status()会返回res对象本身供后续调用.json()等方法。

import { errorHandler } from './path-to-your-error-handler';
// 导入你业务代码中定义的PAYLOAD_TOO_LARGE常量
import { PAYLOAD_TOO_LARGE } from './path-to-your-constants';

describe('errorHandler 中间件测试', () => {
  let handler;
  let mockReq;
  let mockRes;
  let mockNext;
  let mockStatusFn;
  let mockJsonFn;

  // 每个用例执行前重置mock,避免用例间状态污染
  beforeEach(() => {
    // 先执行工厂函数拿到真正的中间件处理逻辑
    handler = errorHandler();
    mockStatusFn = jest.fn().mockReturnThis(); // 链式调用返回自身
    mockJsonFn = jest.fn();
    mockRes = {
      headersSent: false,
      status: mockStatusFn,
      json: mockJsonFn
    };
    mockNext = jest.fn();
    mockReq = {}; // 该中间件未使用req对象,传空对象即可
  });

  test('entity.too.large类型错误会被转换为PAYLOAD_TOO_LARGE错误返回500', () => {
    const testErr = new Error('request body exceed max size');
    testErr.type = 'entity.too.large';

    handler(testErr, mockReq, mockRes, mockNext);

    expect(mockStatusFn).toHaveBeenCalledWith(500);
    expect(mockJsonFn).toHaveBeenCalledWith({
      success: false,
      errorCode: PAYLOAD_TOO_LARGE
    });
    expect(mockNext).toHaveBeenCalledTimes(1);
  });

  test('响应头已发送时不会重复写入响应', () => {
    mockRes.headersSent = true;
    const testErr = new Error('unknown error');
    testErr.errorCode = 'INTERNAL_ERROR';

    handler(testErr, mockReq, mockRes, mockNext);

    expect(mockStatusFn).not.toHaveBeenCalled();
    expect(mockJsonFn).not.toHaveBeenCalled();
    expect(mockNext).toHaveBeenCalledTimes(1);
  });

  test('普通错误会返回对应错误码的500响应', () => {
    const testErr = new Error('db connect failed');
    testErr.errorCode = 'DB_CONNECT_FAILED';

    handler(testErr, mockReq, mockRes, mockNext);

    expect(mockStatusFn).toHaveBeenCalledWith(500);
    expect(mockJsonFn).toHaveBeenCalledWith({
      success: false,
      errorCode: 'DB_CONNECT_FAILED'
    });
    expect(mockNext).toHaveBeenCalledTimes(1);
  });
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 14:24:25