单元测试中伪造 dom 环境的核心是用 jsdom 等轻量虚拟实现替代真实浏览器全局对象,需挂载到 global 或通过 jest/vitest 等框架自动注入,并主动模拟事件、样式及异步行为,避免 mock 底层 api。

在单元测试中伪造 DOM 环境,核心是让原本依赖 window、document、navigator 等浏览器全局对象的 JavaScript 代码,能在 Node.js 环境里正常运行和被测。这不是“绕过 DOM”,而是用轻量、标准兼容的虚拟实现来替代真实浏览器。
用 jsdom 创建可配置的虚拟 DOM
jsdom 是目前最主流、标准兼容性最好的方案。它完整实现了 WHATWG DOM 和 HTML 规范,不依赖浏览器内核,启动快、体积小。
- 安装:执行
npm install --save-dev jsdom - 基础用法:在测试文件顶部创建并注入 DOM 实例
import { JSDOM } from 'jsdom';<br>const dom = new JSDOM('<div id="app"></div>');<br>global.window = dom.window;<br>global.document = dom.window.document; - 关键点:必须把
dom.window和dom.window.document显式挂到global(Node.js 全局对象),否则待测代码访问不到document.querySelector等 API
配合测试框架自动注入(推荐)
手动挂载容易遗漏或重复,各主流测试框架都支持更稳妥的方式:
-
Jest:直接在
jest.config.js中设testEnvironment: 'jsdom',或使用jest-environment-jsdom包。Jest 会为每个测试用例自动创建隔离的 jsdom 实例 -
AVA:需在测试文件中用
beforeEach钩子注入,并确保清理(如调用global.window.close()),避免多个测试共享状态 -
Vitest:推荐搭配
happy-dom(比 jsdom 更轻更快),通过environment: 'happy-dom'启用,无需手动 import 或挂载
模拟特定 DOM 行为(不只是静态结构)
仅提供空 <div> 不够——很多逻辑依赖事件、样式、生命周期。需要主动构造:
<ul>
<li>触发事件:<pre class="brush:php;toolbar:false;">const btn = document.querySelector('button');<br>btn.dispatchEvent(new Event('click'));</pre>
</li>
<li>设置属性/样式:<pre class="brush:php;toolbar:false;">element.style.display = 'block';<br>element.setAttribute('data-testid', 'submit-btn');</pre>
</li>
<li>处理异步渲染:若待测代码含 <code>requestAnimationFrame 或微任务,可在测试中用 await Promise.resolve() 或 await new Promise(setTimeout) 推进队列
注意 jsdom 的边界和替代选择
jsdom 不模拟布局引擎、Canvas 渲染、音频视频解码等,也不支持 window.location.assign 跳转(只更新 href 属性)。对纯 DOM 操作足够,但涉及视觉或导航逻辑时需留意:
- 如只需基础 DOM + 事件,
happy-dom启动更快、内存占用更低,适合原生 JS 组件测试 - 如需完整浏览器行为(如 iframe、跨域策略、真实 CSS 计算),应考虑 Puppeteer 或 Playwright 做端到端测试,而非单元测试阶段伪造
- 永远不要在测试中 mock
document.createElement这类底层方法——这破坏了环境一致性;应让 jsdom 提供原生实现,再通过构造 HTML 字符串或 DOM 操作来准备测试场景











