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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 19:03:18