Jest测试中如何等待父函数内子异步函数执行完成?
解决Jest测试异步setTimeout中函数调用的问题
问题根源
你遇到的核心问题有两个:
addNewBookSpy调用次数为0:因为setTimeout属于宏任务,即使启用了fake timers,若未正确推进计时器+等待微任务队列完成,测试会在异步函数执行前就结束。- “测试完成后无法日志”报错:测试结束后仍有未处理的异步任务(setTimeout内的async函数返回的Promise)在执行,导致Jest抛出警告。
解决方案(无需重写父函数)
只需调整测试逻辑,正确使用Jest的fake timers并等待所有异步任务完成:
1. 确保fake timers的正确配置
在测试前后分别启用/恢复真实计时器,避免污染其他测试用例。
2. 推进计时器并等待微任务完成
setTimeout内的async函数会生成Promise,属于微任务队列。推进计时器后,必须等待微任务执行完毕才能断言调用次数。
代码示例
假设你的addMultipleBooks实现如下:
// 待测试函数 async function addNewBook(book) { // 实际异步逻辑,比如调用API } function addMultipleBooks(books) { books.forEach(book => { setTimeout(async () => { await addNewBook(book); }, 100); }); }
对应的测试代码:
describe('addMultipleBooks', () => { beforeEach(() => { // 启用fake timers,若Jest版本≥27,可尝试legacy模式解决兼容问题 jest.useFakeTimers({ legacyFakeTimers: true }); // 模拟addNewBook函数 jest.spyOn(global, 'addNewBook').mockResolvedValue({ success: true }); }); afterEach(() => { // 恢复真实计时器 jest.useRealTimers(); // 清除所有mock jest.restoreAllMocks(); }); test('正确调用addNewBook对应次数', async () => { const testBooks = [{ id: 1, name: 'Book1' }, { id: 2, name: 'Book2' }]; // 执行待测试函数 addMultipleBooks(testBooks); // 推进所有待执行的计时器 jest.runAllTimers(); // 等待所有微任务(Promise)执行完成 await Promise.resolve(); // 断言调用次数和参数 expect(addNewBook).toHaveBeenCalledTimes(testBooks.length); expect(addNewBook).toHaveBeenCalledWith(testBooks[0]); expect(addNewBook).toHaveBeenCalledWith(testBooks[1]); }); });
关键细节说明
- 若使用Jest 27+的默认modern fake timers出现异常,切换到
legacyFakeTimers模式通常能解决兼容性问题。 await Promise.resolve()的作用是让Jest等待微任务队列清空,确保setTimeout内的async函数完全执行完毕。- 必须在测试结束后恢复真实计时器,避免影响其他测试用例的时间相关逻辑。
内容的提问来源于stack exchange,提问作者S.L
相关产品推荐
相关产品推荐

