- 测试
- 前端
【免费下载链接】enzyme
JavaScript Testing utilities for React
导读
getWrappingComponent()是 Enzyme 中与wrappingComponent选项配套的核心方法:当你在mount()或shallow()时传入wrappingComponent选项,Enzyme 会额外渲染一层“包裹组件”作为被测组件的父级,而getWrappingComponent()则返回这层包裹组件对应的ReactWrapper/ShallowWrapper,让你可以在测试中动态更新它的 props、state,从而操控注入到被测组件中的 Context、Store 等外部依赖。读完本文,你将掌握该方法的完整用法、底层实现原理、联动更新机制以及各种边界条件。
本文以 docs/api/ReactWrapper/getWrappingComponent.md 为骨架,结合 ReactWrapper.js、ShallowWrapper.js 源码与 getWrappingComponent.jsx 测试用例展开。
方法签名与返回值
.getwrappingComponent() => ReactWrapper // 来自 mount() .getwrappingComponent() => ShallowWrapper // 来自 shallow()返回值:一个包裹着已渲染wrappingComponent的ReactWrapper(mount场景)或ShallowWrapper(shallow场景)。这个返回值可以被用来更新wrappingComponent的 props、state 等——例如通过setProps()替换一个 mock Store。
注意:
getWrappingComponent()返回的并不是wrappingComponent自身类,而是它渲染出的组件树;在mount场景下其.type()等于wrappingComponent本身,在shallow场景下则指向它渲染出的最外层元素(详见下文“源码原理”)。
使用前提:wrappingComponent与wrappingComponentProps选项
getWrappingComponent()只在构造时传入了wrappingComponent选项的前提下才有意义。根据 mount.md 与 shallow.md 的说明,两个入口共享以下选项:
options.wrappingComponent(ComponentType,可选):一个将渲染为node父级的组件,可用于为被测组件提供 context、Store 等。注意:wrappingComponent必须渲染它的 children,否则 Enzyme 会抛错。options.wrappingComponentProps(Object,可选):如果指定了wrappingComponent,则作为传入它的初始 props。
getWrappingComponent()的典型工作流是:
- 用
mount()/shallow()传入wrappingComponent(如 Redux 的Provider、React Router 的Router); - 调用
wrapper.getWrappingComponent()拿到包裹组件的 wrapper; - 用
setProps()/setState()等 API 动态更新包裹组件,进而影响被测组件读取的 context/store。
官方示例:动态切换 Redux Store
下面是原文档的完整示例,它展示了一个常见场景——测试 React-Redux 组件时,通过getWrappingComponent()将真实的 Store 换成 mock Store:
import { Provider } from 'react-redux'; import { Router } from 'react-router'; import store from './my/app/store'; import mockStore from './my/app/mockStore'; function MyProvider(props) { const { children, customStore } = props; return ( <Provider store={customStore || store}> <Router> {children} </Router> </Provider> ); } MyProvider.propTypes = { children: PropTypes.node, customStore: PropTypes.shape({}), }; MyProvider.defaultProps = { children: null, customStore: null, }; // mount 场景 const wrapper = mount(<MyComponent />, { wrappingComponent: MyProvider, }); const provider = wrapper.getWrappingComponent(); provider.setProps({ customStore: mockStore }); // shallow 场景完全一致,只需把 mount 换成 shallow // const wrapper = shallow(<MyComponent />, { // wrappingComponent: MyProvider, // }); // const provider = wrapper.getWrappingComponent(); // provider.setProps({ customStore: mockStore });关键点在于MyProvider必须渲染children(Provider与Router包裹着{children}),因为wrappingComponent只有渲染出 children,被测的MyComponent才能出现在组件树中,getWrappingComponent()也才有意义。shallow()场景下的完整用法可参见 ShallowWrapper/getWrappingComponent.md,两者 API 形态完全一致,仅返回的 wrapper 类型不同。
源码原理:getWrappingComponent() 是如何实现的
ReactWrapper(mount 场景)
在 ReactWrapper.js 中,getWrappingComponent()的实现位于 L255-L263:
getWrappingComponent() { if (this[ROOT] !== this) { throw new Error('ReactWrapper::getWrappingComponent() can only be called on the root'); } if (!this[OPTIONS].wrappingComponent) { throw new Error('ReactWrapper::getWrappingComponent() can only be called on a wrapper that was originally passed a `wrappingComponent` option'); } return this[WRAPPING_COMPONENT]; }两个前置校验非常明确:
- 只能在根 wrapper 上调用:
this[ROOT] !== this时直接抛错; - 必须原本就传入了
wrappingComponent选项:否则抛错提示“only be called on a wrapper that was originally passed awrappingComponentoption”。
WRAPPING_COMPONENT是在构造时(L122-L134)创建的:当isCustomComponent(options.wrappingComponent, adapter)为真时,Enzyme 会调用渲染器的getWrappingComponentRenderer(),实例化一个内部类WrappingComponentWrapper(L1250-L1263),并把它加入LINKED_ROOTS列表——这意味着包裹组件与主根是“双向联动”的:更新包裹组件会同步更新主根,反之亦然。
WrappingComponentWrapper继承自ReactWrapper,但重写了getWrappingComponent()并直接抛出TypeError,从而阻止对包裹组件 wrapper 再次调用该方法。
ShallowWrapper(shallow 场景)
在 ShallowWrapper.js 中,getWrappingComponent()的实现位于 L536-L543,逻辑与ReactWrapper版本完全对称:
getWrappingComponent() { if (this[ROOT] !== this) { throw new Error('ShallowWrapper::getWrappingComponent() can only be called on the root'); } if (!this[OPTIONS].wrappingComponent) { throw new Error('ShallowWrapper::getWrappingComponent() can only be called on a wrapper that was originally passed a `wrappingComponent` option'); } return this[WRAPPING_COMPONENT]; }Shallow 场景的构造过程更特殊:makeShallowOptions(L344-L370)在传入wrappingComponent时,会调用适配器的wrapWithWrappingComponent()把被测节点包进RootFinder,然后创建WrappingComponentWrapper,并通过getContextFromWrappingComponent()深度渲染包裹组件,把包裹组件产生的 legacy context 与createContext()的 Provider 值合并进主 wrapper 的options.context(L362-L369)。这正是“wrappingComponent 为被测组件提供 context”这一能力的实现基础。
Shallow 版WrappingComponentWrapper(L1769-L1800)额外重写了rerender()与setState():每次调用都会先执行父类逻辑,再调用updatePrimaryRootContext()(L1731-L1761),重新从包裹组件提取 context 并同步给主 wrapper——因此当包裹组件的setState改变了 context 值时,被测组件会随之重新渲染并获得新值。
底层支撑:adapter-utils 的 wrapWithWrappingComponent
各 React 版本适配器共享的 enzyme-adapter-utils/src/Utils.js 中,wrapWithWrappingComponent()(L340-L350)揭示了包装的最终形态:
export function wrapWithWrappingComponent(createElement, node, options) { const { wrappingComponent, wrappingComponentProps } = options; if (!wrappingComponent) { return node; } return createElement( wrappingComponent, wrappingComponentProps, createElement(RootFinder, null, node), ); }即渲染出的树为:wrappingComponent(props 来自wrappingComponentProps)→RootFinder→ 被测节点。而getNodeFromRootFinder()(L329-L338)在找不到RootFinder时会抛出`wrappingComponent` must render its children!——再次印证了“包裹组件必须渲染 children”的硬性要求,否则测试会在早期直接失败。
边界条件与错误场景(来自测试用例)
仓库的共享测试 packages/enzyme-test-suite/test/shared/methods/getWrappingComponent.jsx 覆盖了该方法的完整行为,以下是几个必须注意的边界:
| 场景 | 行为 |
|---|---|
| React ≤ 0.13 适配器 | 构造时即抛TypeError: your adapter does not support \wrappingComponent`. Try upgrading it!` |
在非根 wrapper 上调用(如wrapper.find('div').getWrappingComponent()) | 抛ReactWrapper::getWrappingComponent() can only be called on the root |
| 在包裹组件自己的 wrapper 上再次调用 | 同样抛“only be called on the root”错误(内部类重写了该方法) |
未传入wrappingComponent就调用 | 抛...only be called on a wrapper that was originally passed a \`wrappingComponent\` option |
| 包裹组件不渲染 children | 抛`wrappingComponent` must render its children! |
| 根已 unmount 后更新包裹组件(mount 场景) | 抛The wrapping component may not be updated if the root is unmounted. |
wrappingComponent未渲染 children | 抛`wrappingComponent` must render its children! |
测试同时验证了关键联动语义:
wrappingComponent.setProps()会影响被测组件:更新contextValue后,wrapper.text()随之变为新值;wrappingComponent.setState()会驱动主 wrapper 重新渲染:包裹组件 state 变化后,被测组件树中出现了新增的StateTester节点;- 主根 unmount 后,包裹组件 wrapper 的 children 变为空,验证了两者“链接”关系的解除。
实战技巧:用 getWrappingComponent() 操控 createContext
针对 React ≥ 16.3 的React.createContext(),测试用例演示了更现代的用法——直接把 Provider 放在wrappingComponent中,通过setProps切换 Provider 的 value:
const Context = React.createContext(); function WrappingComponent(props) { const { value, children } = props; return <Context.Provider value={value}>{children}</Context.Provider>; } const wrapper = mount( <Context.Consumer> {(value) => <div>{value}</div>} </Context.Consumer>, { wrappingComponent: WrappingComponent, wrappingComponentProps: { value: 'hello!' }, }, ); const wrappingComponent = wrapper.getWrappingComponent(); expect(wrapper.text()).to.equal('hello!'); wrappingComponent.setProps({ value: 'goodbye!' }); expect(wrapper.text()).to.equal('goodbye!');这里利用了wrappingComponentProps提供初始值,再借getWrappingComponent()返回的 wrapper 完成运行中切换。由于浅渲染场景会经由updatePrimaryRootContext()同步 Provider 值(源码中通过 referential comparison 判断值是否变化以决定是否重渲染),Consumer 能立即读取到新值。
总结
getWrappingComponent()是把“外部依赖注入”与“测试中动态操控”衔接起来的关键 API。记住三条规则即可熟练使用:
- 先传
wrappingComponent选项,才能调用getWrappingComponent(),且只能在根 wrapper 上调用; wrappingComponent必须渲染 children,否则无法挂载被测组件;- 返回值与
mount/shallow对应:ReactWrapper或ShallowWrapper,均可使用setProps、setState等常规 API,且更新会自动联动主 wrapper。
相关参考:ReactWrapper 文档目录,ShallowWrapper 文档目录,以及wrappingComponent选项的完整说明见 mount.md 与 shallow.md。
- 测试
- 前端
【免费下载链接】enzyme
JavaScript Testing utilities for React
相关推荐
深入解析 esp_lcd_jd9165:在 ESP32-P4 上通过 MIPI-DSI 驱动 JD9165 LCD 控制器
深入解析 esp_lcd_jd9165:在 ESP32 P4 上通过 MIPI DSI 驱动 JD9165 LCD 控制器 导读 esp_lcd_jd9165
物联网嵌入式驱动开发硬件开发Qwen Code External Context 扩展:Provider 绑定上下文检索、Mem0 写入与自动召回深度解析
Qwen Code External Context 扩展:Provider 绑定上下文检索、Mem0 写入与自动召回深度解析 Qwen Code 的 Exte
人工智能AI Agent代码智能体工具调用交互助手CLIQwenReact Context(上下文)原理:Provider与Consumer实现全解析
React Context(上下文)原理:Provider与Consumer实现全解析 引言:你还在为组件通信烦恼吗? 在React开发中,组件间的数据传递一直
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考