Jest测试中如何Mock函数式组件未导出的内部函数?
完全可以实现,且不需要引入任何第三方依赖库,也不需要修改业务组件的对外导出逻辑。
这些事件处理函数定义在组件函数的内部作用域,既没有对外导出也没有挂载到全局对象,Jest无法直接获取到函数引用做替换,不需要硬钻作用域取函数,用下面两种原生支持的方案就能解决:
方案1(推荐):Mock内部函数依赖的底层API,零侵入组件代码
你提到函数里存在和Jest不兼容的指令,本质上绝大多数不兼容问题,都是因为Jest默认搭配的jsdom环境没有实现对应DOM API(比如你示例代码里的canvas.getContext('2d')绘图能力、getBoundingClientRect返回值异常等)。
这种场景根本不需要接触组件内部的setTouchPosition、draw函数,只要在测试用例执行前,把这些函数会调用到的底层DOM API提前mock掉即可,内部函数执行时自然会走你mock的逻辑,不会触发不兼容的原生调用。
对应示例组件的测试前置代码参考:
beforeEach(() => { // 构造mock的canvas对象,把内部用到的方法全部替换为jest mock函数 const mockCanvasCtx = { beginPath: jest.fn(), arc: jest.fn(), lineTo: jest.fn(), stroke: jest.fn(), fill: jest.fn() } const mockCanvas = { getBoundingClientRect: jest.fn(() => ({ top: 0, left: 0, width: 300, height: 600 })), getContext: jest.fn(() => mockCanvasCtx) } // 重写getElementById,返回我们构造的mock canvas document.getElementById = jest.fn((id) => { if (id === 'canvas') return mockCanvas return document.createElement(id) }) })
写完前置逻辑后,正常渲染组件、模拟触发对应的touch、鼠标事件即可,内部函数执行时不会再触发jsdom不兼容的逻辑。这个方案完全不碰业务组件的实现细节,属于前端测试的最佳实践。
方案2:拦截事件绑定逻辑,直接替换事件回调
如果你需要直接断言内部事件函数的调用次数、入参,甚至完全替换内部函数的执行逻辑,可以通过重写事件监听的原型方法实现拦截——毕竟所有内部定义的事件处理函数,最终都会通过addEventListener绑定到对应的DOM节点上。
实现逻辑参考:
test('组件事件逻辑校验', () => { // 自定义mock函数 const mockSetTouchPosition = jest.fn((e) => { e.preventDefault() }) const mockDraw = jest.fn() // 暂存原生的事件绑定方法 const originAddEventListener = EventTarget.prototype.addEventListener // 重写事件绑定方法,拦截目标事件的回调替换 EventTarget.prototype.addEventListener = jest.fn(function (eventName, handler, options) { if (eventName === 'touchstart' || eventName === 'touchmove') { // 匹配到目标事件,替换为我们自定义的mock函数 return originAddEventListener.call(this, eventName, mockSetTouchPosition, options) } if (eventName === 'mousemove') { return originAddEventListener.call(this, eventName, mockDraw, options) } // 其他事件保持原有逻辑不变 return originAddEventListener.call(this, eventName, handler, options) }) // 此处正常编写渲染组件、模拟触发事件的测试逻辑 // 触发事件后就可以直接对mockSetTouchPosition、mockDraw做调用断言 // 用例执行完记得还原原生方法,避免污染其他测试用例 afterAll(() => { EventTarget.prototype.addEventListener = originAddEventListener }) })
这个方案同样只用到Jest自带的mock能力,不需要额外安装依赖。
注意事项
不要为了方便测试把组件内部的工具函数、事件函数提到组件外部导出,这种为了测试修改业务代码结构的做法会提升后续的维护成本,没有必要。
内容的提问来源于stack exchange,提问作者juliano.net

