Enzyme 完全渲染 API 指南:使用 `mount` 进行真实 DOM 挂载与 React 组件测试

发布时间:2026/9/21 14:30:45
Enzyme 完全渲染 API 指南:使用 `mount` 进行真实 DOM 挂载与 React 组件测试 测试前端【免费下载链接】enzymeJavaScript Testing utilities for React项目地址https://gitcode.com/gh_mirrors/en/enzyme点击查看免费下载本篇指南围绕 Enzyme 的Full Rendering APImount(...)展开讲解如何将 React 组件真实挂载进 DOM、在 Node 环境中通过 jsdom 搭建可用的浏览器环境以及如何利用mount返回的ReactWrapper完成生命周期、props、事件、state 等各类测试断言。读完本文你将掌握mount的全部参数选项、底层实现原理以及 ReactWrapper 提供的完整测试方法集能直接将其应用到组件测试实战中。什么是 Full DOM Rendering在 Enzyme 的三套渲染 API 中shallow浅渲染只渲染组件自身一层render静态渲染把组件输出为静态 HTML 字符串而mount则执行完整的 DOM 渲染组件会被真正挂载进文档中所有子组件都会被渲染生命周期钩子会被真实触发。Full DOM rendering 适合以下场景组件内部会与 DOM API 交互例如读取document、操作节点、使用ref需要测试被高阶组件HOC包裹的组件因为mount会渲染出完整的组件树需要验证componentDidMount、componentDidUpdate、componentWillUnmount等完整生命周期行为。与浅渲染、静态渲染不同完全渲染会把组件真正挂载进 DOM这意味着如果多个测试共用同一个 DOM测试之间可能互相影响。编写测试时务必牢记这一点必要时使用.unmount()等方法在测试结束后做清理。环境要求为什么mount需要 DOMmount要求全局作用域中存在完整的 DOM API即运行环境至少“看起来像”浏览器环境。如果你不想在真实浏览器中运行测试官方推荐的方式是依赖jsdom——一个完全用 JS 实现的“无头浏览器”。jsdom 的环境要求可以在使用 JSDOM 的官方指南中找到。该指南强调为了获得最佳体验建议在首次requireReact 之前就把 document 加载进全局作用域并且这段脚本必须在 React 代码运行之前执行。它给出了适用于不同 jsdom 版本的 setup 脚本例如 jsdom v10/* setup.js */ const { JSDOM } require(jsdom); const jsdom new JSDOM(!doctype htmlhtmlbody/body/html); const { window } jsdom; function copyProps(src, target) { Object.defineProperties(target, { ...Object.getOwnPropertyDescriptors(src), ...Object.getOwnPropertyDescriptors(target), }); } global.window window; global.document window.document; global.navigator { userAgent: node.js, }; global.requestAnimationFrame function (callback) { return setTimeout(callback, 0); }; global.cancelAnimationFrame function (id) { clearTimeout(id); }; copyProps(window, global);旧版 jsdom~v10则使用jsdom()直接创建 documentconst { jsdom } require(jsdom); global.document jsdom(); global.window document.defaultView; global.navigator { userAgent: node.js, }; function copyProps(src, target) { const props Object.getOwnPropertyNames(src) .filter((prop) typeof target[prop] undefined) .reduce((result, prop) ({ ...result, [prop]: Object.getOwnPropertyDescriptor(src, prop), }), {}); Object.defineProperties(target, props); } copyProps(document.defaultView, global);仓库中还提供了packages/enzyme/withDom.js作为一键装载 DOM 的辅助入口它会引入raf/polyfill并在global.document不存在时尝试require(jsdom).jsdom()创建 document 与 window同时把 window 上的属性拷贝到 global 上若缺少 jsdom 模块会提示你执行npm install jsdom --save-dev。在 mocha 中可以通过--require预先执行 setup 脚本mocha --require setup.js --recursive path/to/test/dir注意jsdom 需要 Node 4 及以上版本如果被 Node 版本所限可改用基于浏览器的测试运行器例如 Karma 指南 中介绍的方式。历史上 enzyme 曾提供过describeWithDOMAPI 来为每个测试重新加载 JSDOM 文档但 React 源码假定require时的global.document是唯一需要关心的 document这种“重载”方式不再被推荐——保证测试之间不泄漏副作用需要由你自行负责。mount的基本用法一个典型的mount测试如下来自 docs/api/mount.mdimport { mount } from enzyme; import sinon from sinon; import Foo from ./Foo; describe(Foo /, () { it(calls componentDidMount, () { sinon.spy(Foo.prototype, componentDidMount); const wrapper mount(Foo /); expect(Foo.prototype.componentDidMount).to.have.property(callCount, 1); }); it(allows us to set props, () { const wrapper mount(Foo barbaz /); expect(wrapper.props().bar).to.equal(baz); wrapper.setProps({ bar: foo }); expect(wrapper.props().bar).to.equal(foo); }); it(simulates click events, () { const onButtonClick sinon.spy(); const wrapper mount(( Foo onButtonClick{onButtonClick} / )); wrapper.find(button).simulate(click); expect(onButtonClick).to.have.property(callCount, 1); }); });三个用例分别验证了挂载时componentDidMount恰好被调用一次通过setProps可以修改 props 并触发重渲染通过find查找节点后simulate模拟点击事件能触发回调。mount(node[, options]) ReactWrapper参数nodeReactElement要渲染的节点。optionsObject可选options.contextObject可选传入组件的 Contextoptions.attachToDOMElement可选将组件挂载到的 DOM 元素options.childContextTypesObject可选为 wrapper 的所有子组件合并的contextTypesoptions.wrappingComponentComponentType可选作为node父组件渲染的组件可用于为node提供 Context 等。用法示例见.getWrappingComponent()。注意wrappingComponent必须渲染它的 childrenoptions.wrappingComponentPropsObject可选指定wrappingComponent时传给它的初始 props。返回值ReactWrapper包裹渲染输出结果的 wrapper 实例。源码中的选项处理与全局配置mount的入口实现非常简单见 packages/enzyme/src/mount.jsexport default function mount(node, options) { return new ReactWrapper(node, null, options); }真正的逻辑在ReactWrapper构造函数中见 packages/enzyme/src/ReactWrapper.js首先校验global.window与global.document是否存在不存在则抛出It looks like you calledmount()without a global document being loaded.——这就是为什么必须先搭建 jsdom 环境通过makeOptions(passedOptions)合并全局配置与本次调用传入的选项校验node是合法 React 元素adapter.isValidElement否则抛出ReactWrapper can only wrap valid elements调用adapter.createRenderer({ mode: mount, ...options })创建挂载模式的渲染器并执行renderer.render(nodes, options.context)若传入了wrappingComponent还会创建WrappingComponentWrapper并建立 linked roots 关联。makeOptions见 packages/enzyme/src/Utils.js会从全局配置configuration.js中的get()读取attachTo、hydrateIn等默认值再与本次options合并最终确定attachTo与hydrateIn的取值。值得注意的合并规则是hydrateIn优先于attachTo且两者同时提供时必须相等attachTo hydrateIn否则抛出TypeError。测试环境的副作用与清理由于mount真实挂载组件测试之间共享同一份 DOM 时容易互相污染。官方文档明确建议在测试之间使用.unmount()或类似手段进行清理。.unmount()会调用渲染器的unmount()并更新 wrapper源码位置模拟组件经历卸载/挂载的完整生命周期对应的.mount()方法则可以重新挂载组件。此外.detach()可以将组件从attachTo指定的 DOM 节点上卸载。ReactWrapper API 全览mount返回的ReactWrapper提供了完整的测试方法集。以下按功能分类整理每条均可跳转对应文档查找与筛选.find(selector) ReactWrapper查找渲染树中匹配指定选择器的所有节点.findWhere(predicate) ReactWrapper查找渲染树中使谓词函数返回 true 的所有节点.filter(selector) ReactWrapper移除当前 wrapper 中不匹配选择器的节点.filterWhere(predicate) ReactWrapper移除当前 wrapper 中不通过谓词函数的节点.hostNodes() ReactWrapper移除非宿主节点只保留 HTML 节点.not(selector) ReactWrapper移除匹配选择器的节点.filter()的反操作.children([selector]) ReactWrapper获取当前 wrapper 的所有子节点.childAt(index) ReactWrapper返回指定索引处的子节点 wrapper.parents([selector]) ReactWrapper获取当前节点的所有祖先节点.parent() ReactWrapper获取当前节点的直接父节点.closest(selector) ReactWrapper获取当前节点第一个匹配选择器的祖先.slice([begin[, end]]) ReactWrapper按Array#slice规则返回节点子集.at(index) ReactWrapper、.first() ReactWrapper、.last() ReactWrapper取指定索引、首个、末个节点.forEach(fn) ReactWrapper、.map(fn) Array、.reduce(fn[, initialValue]) Any、.reduceRight(fn[, initialValue]) Any、.tap(intercepter) Self数组式遍历与链式调试。存在性与匹配断言.contains(nodeOrNodes) Boolean判断给定节点或节点数组是否存在于渲染树中源码基于nodeEqual严格比较见 ReactWrapper.js.containsMatchingElement(node) Boolean判断给定 React 元素是否“看起来像”渲染树中的元素宽松匹配.containsAllMatchingElements(nodes) Boolean判断是否所有给定元素都存在.containsAnyMatchingElements(nodes) Boolean判断给定元素中是否有任意一个存在.equals(node) Boolean判断当前根节点渲染树是否与传入节点相等.matchesElement(node) Boolean判断给定元素是否匹配当前渲染树.hasClass(className) Boolean判断根节点是否具有指定类名.is(selector) Boolean判断当前节点是否匹配选择器.exists([selector]) Boolean判断当前节点是否存在或指定选择器是否有匹配结果.isEmpty() Boolean已废弃改用.exists().isEmptyRender() Boolean判断组件是否返回了 falsy 值.some(selector) Boolean、.someWhere(predicate) Boolean、.every(selector) Boolean、.everyWhere(predicate) Boolean对 wrapper 中节点集合做“存在/全部”类断言。读取状态与属性.state([key]) Any读取根组件的 state仅类组件可用.context([key]) Any读取根组件的 context.props() Object、.prop(key) Any读取根组件的全部或指定 props.key() String读取根组件的 key.invoke(propName)(...args) Any调用当前节点上的 prop 函数并返回其返回值.instance() ReactComponent|DOMComponent获取 wrapper 底层的组件实例.get(index) ReactElement、.getElement() ReactElement、.getElements() ArrayReactElement取出被包裹的 React 元素.getDOMNode() DOMComponent获取最外层的 DOM 节点源码通过adapter.nodeToHostNode(n, true)实现.ref(refName) ReactComponent | HTMLElement按 ref 名称取节点。更新与交互.simulate(event[, mock]) ReactWrapper在当前节点上模拟事件.simulateError(error) ReactWrapper模拟自定义组件渲染抛出错误.setState(nextState) ReactWrapper手动设置根组件 state.setProps(nextProps[, callback]) ReactWrapper手动设置根组件 props.setContext(context) ReactWrapper手动设置根组件 context.render() CheerioWrapper返回当前节点子树渲染为 HTML 后的 CheerioWrapper.renderProp(key)() ReactWrapper返回由指定 render prop 渲染的节点 wrapper.getWrappingComponent() ReactWrapper若传入了wrappingComponent返回代表它的 wrapper.update() ReactWrapper将 enzyme 的组件树快照与 React 组件树同步.unmount() ReactWrapper、.mount() ReactWrapper卸载 / 重新挂载组件.detach() void从挂载的 DOM 节点上卸载组件。输出与调试.text() String返回当前渲染树中文本节点的字符串表示.html() String返回当前节点的静态 HTML 渲染结果.debug() String返回当前渲染树的字符串表示便于调试.type() String|Function、.name() String返回当前节点的类型与名称。使用wrappingComponent注入 Context当需要为被测组件提供 Context 时可以不用或无法用options.context而是传入一个作为父组件渲染的wrappingComponentimport { mount } from enzyme; import { ThemeProvider } from ./theme; const wrapper mount(MyComponent /, { wrappingComponent: ThemeProvider, wrappingComponentProps: { theme: dark }, }); // 通过 getWrappingComponent() 获取并操作外层组件 wrapper.getWrappingComponent().setProps({ theme: light });需要注意wrappingComponent必须渲染其 children否则node不会出现在渲染树中并且只有支持该特性的 adapter例如 packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js 所对应的版本才能使用否则mount会抛出your adapter does not supportwrappingComponent的TypeError见 ReactWrapper.js 构造函数。完整示例可参考.getWrappingComponent()文档。小结mount是 Enzyme 三种渲染方式中唯一执行真实 DOM 挂载的 API它以jsdom等“类浏览器”环境为前提把组件连同完整子树渲染进文档从而能够覆盖生命周期、DOM 交互、高阶组件包裹等测试场景。使用mount(node[, options])时可通过context、attachTo、childContextTypes、wrappingComponent、wrappingComponentProps等选项精确控制渲染行为返回值ReactWrapper提供了查找、断言、状态读写、事件模拟、生命周期控制等一整套测试方法。由于所有mount测试共享同一个 DOM务必通过.unmount()等方式做好测试间清理保证测试的确定性与隔离性。赞分享测试前端【免费下载链接】enzymeJavaScript Testing utilities for React项目地址https://gitcode.com/gh_mirrors/en/enzyme点击查看免费下载相关推荐Enzyme 使用指南React 组件测试的安装、适配器配置与 shallow/mount/render 三大渲染 API 实战Enzyme 使用指南React 组件测试的安装、适配器配置与 shallow/mount/render 三大渲染 API 实战 Enzyme 是专为 Rea测试前端Enzyme测试React组件逻辑条件渲染验证Enzyme测试React组件逻辑条件渲染验证 你是否曾因React组件条件渲染逻辑复杂而测试困难是否在修改组件后难以确保所有分支都正常工作本文将带你使用测试前端Enzyme测试React组件列表渲染与交互验证Enzyme测试React组件列表渲染与交互验证 在React应用开发中组件列表是最常见的UI结构之一。无论是商品列表、评论流还是数据表格都需要确保其渲染测试前端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考