如何在Jest中测试无参数的Express错误处理中间件
为什么该中间件采用返回处理函数的形式
这是Express生态中非常通用的中间件工厂模式:
- 常规直接接收
error/req/res/next参数的是可直接注册的中间件,使用时写法为app.use(普通中间件) - 你提供的
errorHandler本身不是直接给Express调用的中间件,而是用于生成中间件的工厂函数,使用时需要先执行拿到真正的处理逻辑,写法为app.use(errorHandler())
这种写法的核心价值是预留配置扩展能力,后续如果要给错误处理增加自定义逻辑(比如错误日志上报开关、自定义错误码映射、自定义响应格式),只需要给errorHandler增加入参即可,比如后续可扩展为errorHandler({ enableLog: true }),不需要改动中间件核心结构和注册逻辑。
Jest测试实现思路
测试逻辑和普通Express中间件没有本质区别,核心步骤只有两步:
- 先执行
errorHandler(),拿到真正符合Express错误中间件签名的处理函数 - 构造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
相关产品推荐
相关产品推荐

