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

明明已调用模拟函数,Jest却断言未调用?问题排查

问题:Jest测试无法验证logger.error的调用次数

业务代码

const logger = require('../../../common/lib/logger')();

const displayErrors = async (errors) => {
  const promisesToWriteData = [];

  errors.forEach((error) => {
    console.log({error});
    promisesToWriteData.push(logger.error(`${error[0]}: ${error[1]} - Customer: ${error[2]}`));
  });
  await Promise.all(promisesToWriteData);
};

测试代码

const logger = require('../../../../common/lib/logger');

jest.mock('../../../../common/lib/logger');

const logInfoMock = jest.fn();
const logErrorMock = jest.fn();
logger.mockImplementation(() => ({
  info: logInfoMock,
  error: logErrorMock,
}));

const { displayErrors } = require('../../lib/verification-utils');

const errorArray = [
  [
    'customerData.PaymentDetails.CasePaymentGroup.PAXNominee',
    'NomineeID missing',
    'example4',
  ],
  [
    'customerData.PaymentDetails.CasePaymentGroup.PAXNominee',
    'Comments missing',
    'example4',
  ],
];

describe('Display errors', () => {
  it('should send errors to the logger when there are errors in the error array', async () => {
    const flushPromises = () => new Promise(setImmediate);
    await displayErrors(errorArray);
    await flushPromises();
    expect(logger).toHaveBeenCalledTimes(2);
  });
});

测试报错

expect(jest.fn()).toHaveBeenCalled()

    Expected number of calls: >= 2
    Received number of calls:    0

      34 |     await displayErrors(errorArray);
      35 |     await flushPromises();
    > 36 |     expect(logger).toHaveBeenCalled();
         |                    ^
      37 |   });
      38 | });
      39 |

已知业务代码中的console.log已输出错误数组,说明逻辑执行到了调用logger.error的位置,但测试断言始终失败。


问题根源

  1. 断言对象错误:测试中错误断言了logger工厂函数的调用次数,但业务代码里logger工厂函数在模块加载时仅执行一次(require('...')()),后续调用的是实例上的error方法,正确的断言对象应该是logErrorMock。
  2. Mock函数返回值不符合异步要求:业务代码将logger.error的返回值加入Promise数组并通过await Promise.all等待,但默认Jest mock函数返回undefined而非Promise,导致异步流程无法正确等待。

修复方案

修改测试代码

  1. 将断言对象改为logErrorMock,验证其被调用2次;
  2. 让logErrorMock返回resolved状态的Promise,匹配业务代码的异步场景;
  3. 移除不必要的flushPromises,因为await Promise.all已能确保所有异步操作完成。

修改后的测试代码:

const logger = require('../../../../common/lib/logger');

jest.mock('../../../../common/lib/logger');

const logInfoMock = jest.fn();
// 让mock返回Promise,适配业务代码的异步逻辑
const logErrorMock = jest.fn(() => Promise.resolve());
logger.mockImplementation(() => ({
  info: logInfoMock,
  error: logErrorMock,
}));

const { displayErrors } = require('../../lib/verification-utils');

const errorArray = [
  [
    'customerData.PaymentDetails.CasePaymentGroup.PAXNominee',
    'NomineeID missing',
    'example4',
  ],
  [
    'customerData.PaymentDetails.CasePaymentGroup.PAXNominee',
    'Comments missing',
    'example4',
  ],
];

describe('Display errors', () => {
  it('should send errors to the logger when there are errors in the error array', async () => {
    await displayErrors(errorArray);
    // 断言error方法的调用次数与参数
    expect(logErrorMock).toHaveBeenCalledTimes(2);
    expect(logErrorMock).toHaveBeenNthCalledWith(1, 
      'customerData.PaymentDetails.CasePaymentGroup.PAXNominee: NomineeID missing - Customer: example4'
    );
    expect(logErrorMock).toHaveBeenNthCalledWith(2, 
      'customerData.PaymentDetails.CasePaymentGroup.PAXNominee: Comments missing - Customer: example4'
    );
  });
});

业务代码可选优化

如果logger.error本身不是异步方法,业务代码无需保持异步逻辑,可简化为:

const logger = require('../../../common/lib/logger')();

const displayErrors = (errors) => {
  errors.forEach((error) => {
    console.log({error});
    logger.error(`${error[0]}: ${error[1]} - Customer: ${error[2]}`);
  });
};

对应的测试代码也可去掉Promise相关处理,直接断言logErrorMock的调用次数即可。


内容的提问来源于stack exchange,提问作者Bragon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 22:35:22