Jest无法覆盖Debounce装饰器修饰的函数,求解决方法
如何用Jest覆盖带Debounce装饰器的私有方法代码
问题
我需要用Jest覆盖带有@Debounce(100)装饰器的私有方法checkDataToPositionInStep,但运行npm run coverage时,装饰器下方的代码完全没被统计到覆盖。直接调用该方法也达不到预期效果,请问怎么实现这些代码的覆盖?
待覆盖代码
@Debounce(100) private checkDataToPositionInStep(): void { const proposalConsultData = this.proposalConsultResponseStore.get(); if(proposalConsultData?.documentProposalData?.length > 1) { this.fullScreenLoaderService.hideLoader(); this.router.navigate(['proposta-enviada']); return; } if(proposalConsultData?.warrantyData?.plateUf) { this.fullScreenLoaderService.hideLoader(); this.router.navigate(['upload']); } if(proposalConsultData?.bankData?.branchCode) { this.fullScreenLoaderService.hideLoader(); this.scrollService.next(STEP_ACCORDION.DADOS_GARANTIA.STEP); this.stepperService.next(STEP_ACCORDION.DADOS_GARANTIA.ID); return; } this.fullScreenLoaderService.hideLoader(); this.scrollService.next(STEP_ACCORDION.DADOS_BANCARIOS.STEP); this.stepperService.next(STEP_ACCORDION.DADOS_BANCARIOS.ID); return; }
Debounce装饰器代码
export function Debounce(timeout: number): Function { return function (target, propertyKey: string, descriptor: PropertyDescriptor) { const original = descriptor.value; descriptor.value = function debounce(...args) { setTimeout(() => { original.apply(this, args); }, timeout); } return descriptor; } }
现有测试尝试
it('Should call checkDataToPositionInStep with only bankData', () => { const proposalConsultMock = <any> { bankData: { branchCode: '01901' } }; (facade as any).checkDataToPositionInStep(proposalConsultMock ); });
解决方案
问题核心是防抖装饰器的异步定时器未被触发,以及测试未正确mock依赖数据,按以下步骤修复:
1. 启用Jest假定时器
防抖逻辑依赖setTimeout,Jest默认同步执行测试,不会等待定时器触发。需要在测试前后切换假/真实定时器:
beforeEach(() => { jest.useFakeTimers(); // 启用假定时器模拟时间流逝 }); afterEach(() => { jest.useRealTimers(); // 测试结束后恢复真实定时器,避免影响其他用例 jest.clearAllMocks(); // 清空所有mock调用记录 });
2. 正确mock依赖数据
原方法从proposalConsultResponseStore.get()获取数据,而非参数,所以测试中传参无效。需要mock该方法返回预设数据:
jest.spyOn(facade['proposalConsultResponseStore'], 'get').mockReturnValue({ bankData: { branchCode: '01901' } });
同时mock其他服务的方法,方便后续断言执行结果:
const hideLoaderSpy = jest.spyOn(facade['fullScreenLoaderService'], 'hideLoader'); const scrollNextSpy = jest.spyOn(facade['scrollService'], 'next'); const stepperNextSpy = jest.spyOn(facade['stepperService'], 'next');
3. 触发定时器执行原方法
调用私有方法后,推进定时器到防抖时间(100ms),触发包裹在setTimeout里的原方法执行:
(facade as any).checkDataToPositionInStep(); jest.advanceTimersByTime(100); // 推进时间100ms,触发原方法逻辑
完整测试代码示例
beforeEach(() => { jest.useFakeTimers(); }); afterEach(() => { jest.useRealTimers(); jest.clearAllMocks(); }); it('Should handle checkDataToPositionInStep with only bankData', () => { // Mock store返回预设数据 jest.spyOn(facade['proposalConsultResponseStore'], 'get').mockReturnValue({ bankData: { branchCode: '01901' } }); // Mock服务方法并监听调用 const hideLoaderSpy = jest.spyOn(facade['fullScreenLoaderService'], 'hideLoader'); const scrollNextSpy = jest.spyOn(facade['scrollService'], 'next'); const stepperNextSpy = jest.spyOn(facade['stepperService'], 'next'); // 调用私有方法 (facade as any).checkDataToPositionInStep(); // 推进定时器,触发原方法执行 jest.advanceTimersByTime(100); // 断言方法调用是否符合预期 expect(hideLoaderSpy).toHaveBeenCalled(); expect(scrollNextSpy).toHaveBeenCalledWith(STEP_ACCORDION.DADOS_GARANTIA.STEP); expect(stepperNextSpy).toHaveBeenCalledWith(STEP_ACCORDION.DADOS_GARANTIA.ID); });
覆盖其他分支
按照同样逻辑,修改proposalConsultResponseStore.get()的返回值,即可覆盖方法内的所有分支:
- 返回
documentProposalData.length > 1的数据,断言router.navigate(['proposta-enviada'])被调用 - 返回
warrantyData.plateUf存在的数据,断言router.navigate(['upload'])被调用 - 返回无上述字段的数据,断言跳转到
DADOS_BANCARIOS相关步骤
内容的提问来源于stack exchange,提问作者Felipe Ken
相关产品推荐
相关产品推荐

