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

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;
}

问题根源及解决方案

核心问题

  1. 方法名拼写错误:业务代码中校验方法是ValidateEntries,但测试代码里spy的是ValidateData,导致断言永远无法命中。
  2. 未控制校验分支:没有mock ValidateEntries的返回值,若其默认返回true,则不会进入调用CreateAlert的分支。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 11:00:56