Jest测试类方法调用:async/await下Spy断言失败问题排查
问题:使用async/await时无法验证Service内部方法的调用
我正在测试一个包含多个内部方法的Service,外部服务已通过Mock实现隔离。示例中Process方法执行数据处理逻辑:若ValidateEntries校验失败,将调用CreateAlert向外部服务发送告警(如邮件)。我需要验证代码符合条件时会调用告警方法,但不希望触发真实外部服务。
核心业务代码
async Process(DateToProcess: number): Promise<void> { const IsWeekend: boolean = IsSaturdayOrSunday(DateToProcess); ... const DataToValidate$: Array<ResponseDTO> = await this.GetData(DateToProcess); const IsValid$: boolean = await this.ValidateEntries(DataToValidate$); if (!IsValid$) { await this.CreateAlert('Data is Invalid'); } }
已搭建的测试环境
用Stubs模拟外部服务:
const AlertData = new ResponseDTO(); const mockAlertService = { provide: AlertsService, useFactory: () => ({ Create: jest.fn().mockResolvedValue(AlertData), Delete: jest.fn().mockResolvedValue(null), GetOne: jest.fn().mockResolvedValue(AlertData), }) } ... const module: TestingModule = await Test.createTestingModule({ providers: [ ValidateDailyTimeEntriesService, mockAlertService, ], }).compile(); service = module.get<ValidateDailyTimeEntriesService>(ValidateDailyTimeEntriesService); AlertssTestService = module.get<AlertsService>(AlertsService);
测试代码及问题
Mock配置可正常返回预设值,但测试无法验证告警是否被创建:
it('should send an alert if there are no times entries and it is not a weekend', async () => { const spyProcess_: jest.SpyInstance = jest.spyOn(service, 'Process'); const spyGetData_: jest.SpyInstance = jest.spyOn(service, 'GetData').mockResolvedValue(TestData); const spyValidateData_: jest.SpyInstance = jest.spyOn(service, 'ValidateData'); const spyCreateAlert_: jest.SpyInstance = jest.spyOn(service, 'CreateAlert').mockResolvedValue(true); await service.Process(20221012); // 调用主函数 expect(spyProcess_).toHaveBeenCalled(); // 这两个断言正常通过 expect(spyProcess_).toHaveBeenCalledWith(20221012); expect(spyGetData_).toHaveBeenCalled(); // 断言正常通过 expect(spyGetData_).toHaveReturnedWith(TestData); expect(spyValidateData_).toHaveBeenCalled(); // 断言失败 expect(spyCreateAlert_).toHaveBeenCalled(); // 断言同样失败 spyCreateAlert_.mockClear(); spyValidateData_.mockClear(); spyGetData_.mockClear(); spyProcessOneDate_.mockClear(); });
错误信息
expect(jest.fn()).toHaveBeenCalled() Expected number of calls: >= 1 Received number of calls: 0 > 298 | expect(spyValidateData_).toHaveBeenCalled(); > 298 | expect(spyCreateAlert_).toHaveBeenCalled();
补充说明
问题看似和async/await与Promise处理方式有关:将Process改为then写法时测试可通过,但使用await写法时测试失败:
- 可通过的
then写法:
async Process(): Promise<void> { this.GetData(DateToProcess).then( (DataToValidate$: Array<ResponseDTO>) => {}); this.CreateAlert('Data is Invalid').then( (Result$: boolean) => {}); return; }
- 测试失败的
await写法:
async Process(): Promise<void> { const DataToValidate$: Array<ResponseDTO> = await this.GetData(DateToProcess); const Result$: boolean = await this.CreateAlert(); return; }
问题根源及解决方案
核心问题
- 方法名拼写错误:业务代码中校验方法是
ValidateEntries,但测试代码里spy的是ValidateData,导致断言永远无法命中。 - 未控制校验分支:没有mock
ValidateEntries的返回值,若其默认返回true,则不会进入调用CreateAlert的分支。 - Spy对象逻辑偏差:若
CreateAlert是封装外部服务的方法,要么spy当前Service的CreateAlert,要么直接验证注入的AlertsService的Create方法(更直接,因为Mock的是外部服务)。
修正后的测试代码
it('should send an alert if there are no times entries and it is not a weekend', async () => { // 1. Mock GetData返回测试数据 jest.spyOn(service, 'GetData').mockResolvedValue(TestData); // 2. Mock ValidateEntries返回false,触发告警分支 jest.spyOn(service, 'ValidateEntries').mockResolvedValue(false); // 3. Spy当前Service的CreateAlert方法,或直接Spy外部服务的Create方法 const spyCreateAlert = jest.spyOn(service, 'CreateAlert').mockResolvedValue(true); // 可选:直接验证外部服务方法 // const spyAlertCreate = jest.spyOn(AlertssTestService, 'Create'); await service.Process(20221012); // 验证GetData的调用参数 expect(service.GetData).toHaveBeenCalledWith(20221012); // 验证ValidateEntries被调用,且传入正确数据 expect(service.ValidateEntries).toHaveBeenCalledWith(TestData); // 验证CreateAlert被调用并传入正确告警信息 expect(spyCreateAlert).toHaveBeenCalledWith('Data is Invalid'); // 可选:验证外部服务方法被调用 // expect(spyAlertCreate).toHaveBeenCalledWith('Data is Invalid'); // 统一清理Mock jest.clearAllMocks(); });
关于async/await与then的差异说明
- 使用
then时测试“假通过”是因为代码非阻塞,测试在then内逻辑执行完毕前就完成了断言,属于假阳性结果。 - 使用
await才是正确的异步测试方式,测试会等待Process方法完全执行完毕再断言。之前的失败完全是因为拼写错误和未正确Mock分支条件,和async/await本身无关。
内容的提问来源于stack exchange,提问作者Steven Scott
相关产品推荐
相关产品推荐

