ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

使用 Enzyme 测试 React Native 组件:Adapter 配置、JSDOM 环境模拟与完整实战指南

2026/9/20 9:37:39 拓冰建站 浏览量
使用 Enzyme 测试 React Native 组件:Adapter 配置、JSDOM 环境模拟与完整实战指南 测试前端【免费下载链接】enzymeJavaScript Testing utilities for React项目地址https://gitcode.com/gh_mirrors/en/enzyme点击查看免费下载导读React Native 组件的单元测试长期受限于其依赖宿主设备的复杂环境难以接入 CI。本文以 Enzyme 官方指南为基础系统讲解如何在 React Native 项目中配置 Enzyme Adapter、加载 JSDOM 模拟 DOM并借助testID与findWhere等 API 完成组件查找、事件模拟与快照测试。读完本文你将掌握一套可直接复制到 Jest、Mocha 等测试框架中运行的 React Native 测试方案并理解其底层原理Adapter 校验、mount依赖 DOM 的原因、React Navigation 快照失败的规避方法。为什么 React Native 组件可以用 Enzyme 测试自 React Native v0.18 起React Native 将 React 作为普通依赖引入而不再维护一个 React 的分支fork。这意味着 React 生态中的测试工具——包括 Enzyme 的shallow渲染——可以直接作用于 React Native 组件。不过React Native 本身带有大量环境级依赖原生模块、触摸事件、宿主平台 API 等这些依赖在没有真实设备或模拟器的环境中很难被模拟。因此当你希望在 Travis 等典型 CI 服务器上运行测试套件时就需要额外补齐两样东西一个 Enzyme Adapter让 Enzyme 知道当前被测的是哪个 React 版本、如何遍历其渲染树一个模拟的 DOMemulated DOM供mount完整 DOM 渲染挂载组件使用。配置 Enzyme Adapter基础配置代码在写任何测试之前必须调用Enzyme.configure注册 Adapter。React Native 专用的 Adapter 仍在社区讨论中对应 enzyme 仓库 issue #1436因此在专用 Adapter 出现之前官方推荐直接使用标准 Web Adapter例如enzyme-adapter-react-16import Adapter from enzyme-adapter-react-16; Enzyme.configure({ adapter: new Adapter() });源码视角为什么必须配置 AdapterEnzyme.configure在 packages/enzyme/src/index.js 中被导出为configure其底层实现位于 packages/enzyme/src/configuration.js它先将配置与已有配置合并merge合并时若传入adapter则会调用validateAdapter做严格校验最后通过Object.assign写入全局 configuration。校验逻辑见 packages/enzyme/src/validateAdapter.js非常严格未传入 adapter 时抛出错误提示必须先调用Enzyme.configure({ adapter: new Adapter() })传入的是构造函数而非实例typeof adapter function时报错传入的实例未继承自EnzymeAdapter基类packages/enzyme/src/EnzymeAdapter.js时报错。也就是说配置必须传入 adapter 实例且必须与项目 React 版本匹配。在 React Native 项目中选择哪个版本的标准 Adapter取决于 React Native 内部捆绑的 React 版本本文以 react-16 对应的enzyme-adapter-react-16为例该 Adapter 源码见 packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js。加载模拟 DOMJSDOM 的正确姿势在 React Native 专用 Adapter 出现之前要使用 Enzyme 的mount完整 DOM 渲染就必须先加载一个模拟的 DOM。社区中虽有react-native-mock-renderer等方案但官方推荐的方案是使用JSDOM——一个完全用 JavaScript 实现的 headless 浏览器具体做法与纯 Web 环境下的 enzyme JSDOM 指南见 docs/guides/jsdom.md一致。为什么要模拟 DOMmount的实现packages/enzyme/src/mount.js本质上是new ReactWrapper(node, null, options)而 ReactWrapper 需要把组件真实挂载进 DOM 才能工作。正如 docs/api/mount.md 所述完整 DOM 渲染要求全局作用域中存在一套“看起来像浏览器”的 DOM API。在 Node 环境里JSDOM 就是提供这套 API 的最优选择。关键技术点在 React 首次 require 之前加载 DOM官方在 docs/guides/jsdom.md 中强调最好在 React 被首次 require 之前就把 document 加载进全局作用域。因为 React 源码在 require 时会读取global.document并假定它是唯一需要关心的 document之后再“重新加载” document 会导致各种隐性副作用。这也是为什么官方不再推荐旧版describeWithDOMAPI它在每个测试前动态换 document的原因。一个标准的 setup 脚本如下jsdom v10API/* 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);要点说明new JSDOM(!doctype htmlhtmlbody/body/html)创建了一个最小但完整的 HTML 文档global.window、global.document、global.navigator挂到全局React 才能正常挂载copyProps(window, global)用属性描述符把 window 上的所有属性复制到 global弥补新版 jsdom 不再隐式把属性暴露到全局的差异requestAnimationFrame/cancelAnimationFrame用定时器兜底避免测试环境缺少 rAF 报错。在 Mocha 等框架中可通过--require让该脚本在测试套件运行前执行mocha --require setup.js --recursive path/to/test/dir没有 className 时用 testID 作为选择器标准 React 测试中常用.find(.foo)按 className 查找节点但 React Native 的组件树里没有 DOM className 概念。React Native 为此提供了testIDprop它可以在 Enzyme 中扮演类似className的选择器角色View key{key} style{styles.todo} testIDtodo-item Text testIDtodo-title style{styles.title}{todo.title}/Text /View配合findWhere按 prop 精确查找expect(wrapper.findWhere((node) node.prop(testID) todo-item)).toExist();findWhereAPI 文档见 docs/api/ReactWrapper/findWhere.md会对渲染树中每个节点调用传入的谓词函数返回所有结果为 true 的节点构成的新 wrapper。其底层实现在 packages/enzyme/src/ReactWrapper.js 与 packages/enzyme/src/ShallowWrapper.js 中通过treeFilter完成全树遍历因此node.prop(testID)这种按任意 prop 过滤的方式本质上就是一次全量遍历 谓词筛选可以覆盖 RN 组件树中任意层次的节点。Jest 默认配置setupFilesAfterEnv JSDOM要把上述环境配置接入测试框架官方推荐使用独立 setup 脚本例如通过 Jest 的setupFilesAfterEnv设置加载它。第一步配置 jest.config.js在项目根目录创建或更新jest.config.js加入setupFilesAfterEnv// jest.config.js module.exports { // Load setup-tests.js before test execution setupFilesAfterEnv: rootDirsetup-tests.js, // ... };第二步编写 setup-tests.js然后创建或更新setupFilesAfterEnv指定的文件本例为项目根目录的setup-tests.js在测试执行前完成两件事模拟 DOM 配置 Enzyme// setup-tests.js import react-native; import jest-enzyme; import Adapter from enzyme-adapter-react-16; import Enzyme from enzyme; /** * Set up DOM in node.js environment for Enzyme to mount to */ 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, }; copyProps(window, global); /** * Set up Enzyme to mount to DOM, simulate events, * and inspect the DOM in tests. */ Enzyme.configure({ adapter: new Adapter() });这段脚本包含三层职责import react-native提前加载 RN 运行时并确保它在 JSDOM 全局就绪之后才被求值import jest-enzyme引入 jest-enzyme 的匹配器toExist()等断言即来源于此Enzyme.configure({ adapter: new Adapter() })完成上一节所述的 Adapter 注册。其他测试库把 JSDOM 开箱即用地塞进单个测试文件如果你使用的不是 Jest 的全局环境配置或者希望只在个别测试文件里启用 DOM可以采用“按文件启用 jsdom 环境”的方式。同样先写好一个不含 JSDOM 的轻量 setupimport react-native; import jest-enzyme; import Adapter from enzyme-adapter-react-16; import Enzyme from enzyme; /** * Set up Enzyme to mount to DOM, simulate events, * and inspect the DOM in tests. */ Enzyme.configure({ adapter: new Adapter() });创建独立的测试文件然后创建一个以enzyme.test.js或enzyme.test.ts命名的独立测试文件例如component.enzyme.test.js/** * jest-environment jsdom */ import React from react; import { mount } from enzyme; import { Text } from ../../../component/text; describe(Component tested with enzyme, () { test(App mount with enzyme, () { const wrapper mount(Text /); // other tests operations }); });最关键的一点是确保测试在jestEnvironment为jsdom时运行——最简单的方式就是在文件顶部加一行/* jest-environment jsdom */注释。这样 JSDOM 环境会在该文件运行时按需建立mount便有 DOM 可挂载其他不使用mount的测试文件则保持默认的 node 环境互不干扰。做完以上配置你就可以开始正式编写测试了。完整实战Todo 列表的 Enzyme 测试下面是一个完整的 Todo 列表演示覆盖了前文所有技巧并额外展示了 React Native 测试中两个常见的坑原生组件的事件模拟与快照测试的随机 key 问题。import React from react; import renderer from react-test-renderer; import { mount, ReactWrapper } from enzyme; import { Provider } from mobx-react; import { Text } from native-base; import { TodoItem } from ./todo-item; import { TodoList } from ./todo-list; import { todoStore } from ../../stores/todo-store; // React Navigation generates random React keys, which makes // snapshot testing fail. Mock the randomness to keep from failing. jest.mock(react-navigation/src/routers/KeyGenerator, () ({ generateKey: jest.fn(() 123), })); describe(todo-list, () { describe(enzyme tests, () { it(can add a Todo with Enzyme, () { const wrapper mount( Provider keyLength{0} todoStore{todoStore} TodoList / /Provider, ); const newTodoText I need to do something...; const newTodoTextInput wrapper.find(Input).first(); const addTodoButton wrapper .find(Button) .findWhere((w) w.text() Add Todo) .first(); newTodoTextInput.props().onChangeText(newTodoText); // Enzyme usually allows wrapper.simulate() alternatively, but this doesnt support press events. addTodoButton.props().onPress(); // Make sure to call update if external events (e.g. Mobx state changes) // result in updating the component props. wrapper.update(); // You can either check for a testID prop, similar to className in React: expect( wrapper.findWhere((node) node.prop(testID) todo-item), ).toExist(); // Or even just find a component itself, if you broke the JSX out into its own component: expect(wrapper.find(TodoItem)).toExist(); // You can even do snapshot testing, // if you pull in enzyme-to-json and configure // it in snapshotSerializers in package.json expect(wrapper.find(TodoList)).toMatchSnapshot(); }); }); });逐段拆解1. 事件模拟simulate并不万能React Native 中没有浏览器事件模型wrapper.simulate(press)这类调用无法触发 RN 的原生触摸事件。因此示例采用直接调用 props 回调的方式newTodoTextInput.props().onChangeText(newTodoText)模拟输入addTodoButton.props().onPress()模拟点击。simulate的适用场景API 说明见 docs/api/ReactWrapper/simulate.md是模拟 DOM 事件并可选传入 mock 事件对象在 RN 环境中其限制正如示例注释所写——不支持press等 RN 原生事件。2. 查找策略组合使用find与findWherewrapper.find(Input).first()按组件名/类型查找wrapper.find(Button).findWhere((w) w.text() Add Todo).first()先按类型过滤再用谓词按文本内容精确定位wrapper.findWhere((node) node.prop(testID) todo-item)按testIDprop 定位等效于 Web 端按 className 查找wrapper.find(TodoItem)直接按组件类查找前提是该子组件已拆分为独立 JSX 组件。3. 外部状态变更后必须wrapper.update()MobX 等外部状态库引起的 props 更新不会自动同步到 Enzyme 的组件树快照。update()的作用是重新同步 enzyme 组件树快照与 React 组件树见 docs/api/ReactWrapper/update.md所以在触发外部状态变更后、断言之前调用它是必须的。4. 快照测试解决随机 key 问题示例最后用toMatchSnapshot()做快照断言但 React Navigation 会为路由生成随机 React key导致快照每次运行都不同、永远失败。解决方案是在测试中 mock 掉react-navigation/src/routers/KeyGenerator让generateKey恒返回固定值jest.fn(() 123)从而保证快照稳定。另外需要说明两点快照断言需配合enzyme-to-json并在package.json中配置snapshotSerializers才能输出稳定的 Enzyme 树结构官方指南同时指出Jest 快照测试虽然可以配合 JSDOM 方案使用但并不鼓励且仅支持通过wrapper.debug()输出调试字符串见 docs/api/ReactWrapper/debug.md。补充注意事项额外的原生组件 mock根据被测组件依赖的原生模块不同你可能还需要对原生组件进行额外的 mocking环境加载顺序JSDOM 文档必须在 React 首次 require 之前进入全局作用域见上文“关键技术点”这是整套方案稳定运行的前提Adapter 版本匹配Adapter 必须与项目实际使用的 React 版本一致选错版本会导致渲染树遍历异常Adapter 校验规则见 packages/enzyme/src/validateAdapter.js测试间隔离mount是真实 DOM 渲染多个测试共用同一 DOM 会产生相互影响必要时用.unmount()或.detach()清理完整 ReactWrapper API 清单见 docs/api/mount.md。总结在 React Native 中接入 Enzyme核心只需三步用Enzyme.configure注册与 React 版本匹配的标准 Adapter用 JSDOM 在测试环境建立模拟 DOM并确保其在 React 加载前就绪通过testIDfindWhere弥补 className 缺失以 props 回调代替simulate完成事件驱动。配合 Jest 的setupFilesAfterEnv与按文件启用的jest-environment jsdom这套方案可以稳定地跑在 Travis 等 CI 服务器上让 React Native 组件测试拥有与 Web 侧一致的 Enzyme 体验。赞分享测试前端【免费下载链接】enzymeJavaScript Testing utilities for React项目地址https://gitcode.com/gh_mirrors/en/enzyme点击查看免费下载相关推荐T3 Code HTTP边界实现详解http、ws、CORS与响应错误防护如何配合的完整指南T3 Code HTTP边界实现详解http、ws、CORS与响应错误防护如何配合的完整指南 T3 Code 是一款面向开发者的 AI 编程协作工具它的本地AI Agent代码智能体后端前端移动开发桌面应用Enzyme在React Native组件测试中的实践指南Enzyme在React Native组件测试中的实践指南 前言 在React Native应用开发中组件测试是保证应用质量的重要环节。Enzyme作为Rea测试前端Tack高级配置深入理解Terraform模块化架构设计Tack高级配置深入理解Terraform模块化架构设计 Tack作为基于Terraform的Kubernetes集群部署工具其模块化架构设计是实现灵活配置创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考