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

如何在Jest测试中通过JSDOM成功执行动态创建的JS脚本?

问题:Jest+jsdom测试DOM操作模块失败,脚本未执行

我有一个封装DOM操作便捷方法的模块,用Jest和jsdom做测试时遇到问题——测试里dom.window.document.addEventListener('DOMContentLoaded')内的断言不通过,../mocks/forTesting.js里的console.log也没输出。目标是让脚本执行,把id为"container"的div内容改成'test'。


测试代码

import { runScript } from '../export/script.js';
import { JSDOM } from 'jsdom'
const dom = new JSDOM(`<!DOCTYPE html><html><body><div id="container">Hello</div></body></html>`, { resources: 'usable',  runScripts: "dangerously" })
global.window = dom.window
global.document = dom.window.document

describe("Rendering Module", () => {
   test("Verify if mocked script was executed", async () => {
        const script = { name: 'testScript', value: `<script src="../mocks/forTesting.js" ></script>` }
        const scriptFreezed = Object.freeze(script);
        await runScript(scriptFreezed, dom.window.document);
    
        dom.window.document.addEventListener('DOMContentLoaded', () => {
            const text = dom.window.document.getElementById('container').innerHTML;
            expect(text).toEqual('test')
        });

        dom.window.document.dispatchEvent(new dom.window.Event('DOMContentLoaded', {"bubbles": true}))
    })
})

报错信息

console.error
    Error: Uncaught [Error: expect(received).toEqual(expected) // deep equality

    Expected: "test"
    Received: "Hello"

../mocks/forTesting.js代码

window.document.addEventListener("DOMContentLoaded", function (event) {
    console.log('forTesting.js')
    window.document.getElementById('container').innerHTML = 'test';
});

runScript代码

const runScript = (script, dom) => {
    const node = dom.createRange().createContextualFragment(script.value);
    dom.head.appendChild(node);
}

问题原因及修复方案

核心问题

  1. 脚本加载异步未处理:runScript只是把script标签插入DOM,但脚本加载是异步操作,直接await它没用(因为它没返回Promise),导致你提前触发了DOMContentLoaded事件,此时脚本还没加载完成,监听逻辑根本没注册上。
  2. 事件触发顺序错误:先触发事件再等脚本加载,脚本里的监听自然不会执行。
  3. 相对路径可能失效:jsdom环境下相对路径加载脚本容易找不到文件,导致脚本根本没被执行。

修复步骤

1. 改造runScript,支持等待脚本加载完成

修改runScript,返回Promise监听脚本的加载状态:

const runScript = (script, dom) => {
    return new Promise((resolve, reject) => {
        const fragment = dom.createRange().createContextualFragment(script.value);
        const scriptElement = fragment.querySelector('script');
        dom.head.appendChild(fragment);
        
        // 监听脚本加载完成/失败事件
        scriptElement.onload = resolve;
        scriptElement.onerror = reject;
    });
}

2. 调整测试逻辑,确保脚本加载后再触发事件

用绝对路径加载脚本,避免路径问题,同时等待脚本加载完成后再处理事件:

import { runScript } from '../export/script.js';
import { JSDOM } from 'jsdom'
import path from 'path';
import { fileURLToPath } from 'url';

// 生成脚本的绝对路径
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const scriptAbsolutePath = path.join(__dirname, '../mocks/forTesting.js');

const dom = new JSDOM(`<!DOCTYPE html><html><body><div id="container">Hello</div></body></html>`, { 
    resources: 'usable',  
    runScripts: "dangerously",
    url: 'http://localhost' // 设置base URL,避免资源加载跨域问题
})

const { window, document } = dom;

describe("Rendering Module", () => {
   test("Verify if mocked script was executed", async () => {
        const script = { 
            name: 'testScript', 
            value: `<script src="${scriptAbsolutePath}" ></script>` 
        };
        const scriptFreezed = Object.freeze(script);
        
        // 等待脚本加载完成
        await runScript(scriptFreezed, document);
    
        // 准备断言变量
        let containerContent;
        document.addEventListener('DOMContentLoaded', () => {
            containerContent = document.getElementById('container').innerHTML;
        });

        // 触发事件
        document.dispatchEvent(new window.Event('DOMContentLoaded', { bubbles: true }));

        // 执行断言
        expect(containerContent).toEqual('test');
    })
})

3. 可选:简化forTesting.js逻辑(非必须)

因为jsdom初始化时页面已经解析完成,不需要再等DOMContentLoaded,可以直接执行逻辑:

console.log('forTesting.js')
window.document.getElementById('container').innerHTML = 'test';

如果一定要保留DOMContentLoaded监听,上面的测试逻辑已经能保证事件触发时机正确。


内容的提问来源于stack exchange,提问作者Alice Arruda Atalla

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 03:00:52