明明已调用模拟函数,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的位置,但测试断言始终失败。
问题根源
- 断言对象错误:测试中错误断言了
logger工厂函数的调用次数,但业务代码里logger工厂函数在模块加载时仅执行一次(require('...')()),后续调用的是实例上的error方法,正确的断言对象应该是logErrorMock。 - Mock函数返回值不符合异步要求:业务代码将
logger.error的返回值加入Promise数组并通过await Promise.all等待,但默认Jest mock函数返回undefined而非Promise,导致异步流程无法正确等待。
修复方案
修改测试代码
- 将断言对象改为
logErrorMock,验证其被调用2次; - 让
logErrorMock返回resolved状态的Promise,匹配业务代码的异步场景; - 移除不必要的
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
相关产品推荐
相关产品推荐

