使用Jest测试setTimeout功能时遇到问题求助
问题描述
我正在测试一个基于Bootstrap Alert组件的提示功能——先向用户展示提示框,3000毫秒后自动关闭。但测试发现调用超时函数后,提示框的类仍然存在,想确认我的测试方式是否正确?
待测试代码
setTimeout(function () { let messages = document.getElementById("msg"); let alert = new bootstrap.Alert(messages); alert.close(); }, 3000);
测试代码(script.test.js)
jest.useFakeTimers(); describe("Timer alerts and then closes after 3000", () => { beforeAll(() => { let fs = require("fs"); let fileContents = fs.readFileSync("templates/base.html", "utf-8"); document.open(); document.write(fileContents); document.close(); }); test('closes the alert after 3 second via advanceTimersByTime', () => { const element = document.getElementById("msg"); expect(element).not.toBeNull(); require('../script'); expect(document.querySelector('.alert')).toBeTruthy(); jest.runAllTimers(); jest.advanceTimersByTime(3000); expect(document.querySelector('.alert')).toBeNull(); }); });
测试方式的问题及修正
你的测试代码存在几个关键问题,导致断言失败:
- 重复触发定时器:同时调用
jest.runAllTimers()和jest.advanceTimersByTime(3000)是冗余且错误的。runAllTimers()会立即执行所有待处理的定时器,后续再调用advanceTimersByTime没有意义,反而会干扰测试逻辑。只需保留advanceTimersByTime(3000)即可。 - Bootstrap Alert关闭的异步特性:
alert.close()不是同步移除元素,它会先执行关闭动画,动画结束后才会修改DOM(移除元素或删除类名)。这个过程是异步的,直接断言会因为动画未完成而失败。 - 模块加载时机不合理:
require('../script')应该提前到测试初始化阶段,确保定时器被正确注册到fake timers中。
修正后的测试代码(模拟Bootstrap行为)
jest.useFakeTimers(); // 模拟Bootstrap Alert的close方法,绕过真实动画的异步延迟 jest.mock('bootstrap', () => ({ Alert: jest.fn().mockImplementation(() => ({ close: jest.fn(() => { const msgEl = document.getElementById("msg"); msgEl.classList.remove('alert'); msgEl.remove(); }) })) })); describe("Timer alerts and then closes after 3000", () => { beforeAll(() => { let fs = require("fs"); let fileContents = fs.readFileSync("templates/base.html", "utf-8"); document.open(); document.write(fileContents); document.close(); // 提前加载脚本,注册定时器 require('../script'); }); test('closes the alert after 3 seconds', () => { const element = document.getElementById("msg"); expect(element).not.toBeNull(); expect(document.querySelector('.alert')).toBeTruthy(); // 快进3000毫秒触发定时器 jest.advanceTimersByTime(3000); // 验证元素状态 expect(document.querySelector('.alert')).toBeNull(); expect(document.getElementById("msg")).toBeNull(); }); });
另一种方案(等待真实动画完成)
如果不想模拟Bootstrap,可等待关闭动画完成后再断言:
jest.useFakeTimers(); describe("Timer alerts and then closes after 3000", () => { beforeAll(() => { let fs = require("fs"); let fileContents = fs.readFileSync("templates/base.html", "utf-8"); document.open(); document.write(fileContents); document.close(); require('../script'); }); test('closes the alert after 3 seconds', async () => { expect(document.querySelector('.alert')).toBeTruthy(); jest.advanceTimersByTime(3000); // 默认Bootstrap关闭动画时长为150ms,等待动画结束 await new Promise(resolve => setTimeout(resolve, 150)); expect(document.querySelector('.alert')).toBeNull(); }); });
内容的提问来源于stack exchange,提问作者Leonie
相关产品推荐
相关产品推荐

