- 测试
- 前端
【免费下载链接】enzyme
JavaScript Testing utilities for React
在 enzyme 的 ShallowWrapper API 中,.prop(key)是获取浅渲染结果中根节点(root node)指定 prop 值的核心方法。无论是断言组件接收到的 props、取出事件回调手动触发,还是校验 DOM 属性的映射结果,.prop(key)都是高频使用的测试工具。本文将基于仓库文档 docs/api/ShallowWrapper/prop.md,结合 ShallowWrapper 源码 与 共享测试用例,完整讲解该方法的使用规则、参数语义、与instance().props的关键区别,以及底层实现原理。
方法签名与核心语义
.prop(key) => Any- 返回值:
Any,即包装器(wrapper)根节点上、由key指定的 prop 值; - 返回值类型:与传入 prop 时的类型完全一致——字符串返回字符串,函数返回函数,对象返回对象引用;
- 约束条件:必须是一个单节点包装器(single-node wrapper),即调用
find、filter等操作后包装器内只能包含恰好 1 个节点,否则会抛出运行时错误。
参数说明
| 参数 | 类型 | 必填 | 含义 |
|---|---|---|---|
key | String | 是 | prop 名称,等价于读取根节点的this.props[key]或props[key] |
也就是说,wrapper.prop('foo')与直接读取 props 对象的wrapper.props().foo结果一致——事实上,从源码看,prop正是对props()的薄封装:
// packages/enzyme/src/ShallowWrapper.js#L1309-L1311 prop(propName) { return this.props()[propName]; }而props()又会经过单节点校验后调用 RST(React Standard Tree)遍历工具函数取 props:
// packages/enzyme/src/ShallowWrapper.js#L1173-L1175 props() { return this.single('props', propsOfNode); }// packages/enzyme/src/RSTTraversal.js#L9-L11 export function propsOfNode(node) { return (node && node.props) || {}; }propsOfNode对空节点做了容错(返回{}),因此对不存在的 prop 读取会得到undefined而非抛错,这一点在断言时需要注意。
单节点约束:single机制
.prop(key)要求包装器只包含一个节点,这一约束由 ShallowWrapper 内部通用的single辅助方法保证。当包装器中的节点数量不为 1 时,会抛出如下错误:
// packages/enzyme/src/ShallowWrapper.js#L1647-L1654 single(name, fn) { const fnName = typeof name === 'string' ? name : 'unknown'; const callback = typeof fn === 'function' ? fn : name; if (this.length !== 1) { throw new Error(`Method “${fnName}” is meant to be run on 1 node. ${this.length} found instead.`); } return callback.call(this, this.getNodeInternal()); }错误信息形如Method "prop" is meant to be run on 1 node. 3 found instead.。因此,在调用.prop()前应确保使用first()、.at(index)等手段将包装器收敛为单节点,尤其是对.find()可能命中多个元素的情况。
官方示例:prop 读取、事件回调触发与状态联动
仓库文档提供了一个完整的实战示例,同时演示了三件事:读取根节点 prop、通过取出的回调触发事件、以及验证 state 的联动更新。组件定义如下:
import PropTypes from 'prop-types'; import ValidateNumberInputComponent from './ValidateNumberInputComponent'; class MyComponent extends React.Component { constructor(...args) { super(...args); this.state = { number: 0, }; this.onValidNumberInput = this.onValidNumberInput.bind(this); } onValidNumberInput(e) { const number = e.target.value; if (!number || typeof number === 'number') { this.setState({ number }); } } render() { const { includedProp } = this.props; const { number } = this.state; return ( <div className="foo bar" includedProp={includedProp}> <ValidateNumberInputComponent onChangeHandler={onValidNumberInput} number={number} /> </div> ); } } MyComponent.propTypes = { includedProp: PropTypes.string.isRequired, };注意:示例中
onValidNumberInput在 JSX 中直接使用(未加this.),此处省略号用于示意其绑定方式,实际运行请按 React 规范在构造函数中完成this.onValidNumberInput = this.onValidNumberInput.bind(this),或使用 class 属性/箭头函数写法。
第一步:读取根节点 prop
const wrapper = shallow(<MyComponent includedProp="Success!" excludedProp="I'm not included" />); expect(wrapper.prop('includedProp')).to.equal('Success!');这里wrapper.prop('includedProp')读到的"Success!"是MyComponent 渲染出的根 DOM 节点<div>上的includedProp属性,它由render()中的{...}展开传递而来。
第二步:取出回调 prop 并手动触发
const validInput = 1; wrapper.find('ValidateNumberInputComponent').prop('onChangeHandler')(validInput); expect(wrapper.state('number')).to.equal(validInput); const invalidInput = 'invalid input'; wrapper.find('ValidateNumberInputComponent').prop('onChangeHandler')(invalidInput); expect(wrapper.state('number')).to.equal(0);find(...)会返回包含子组件节点的包装器,再对其调用.prop('onChangeHandler')即可取出传入子组件的函数 prop并直接调用,模拟用户输入事件。由于触发的是真实绑定的组件方法,state 会随之更新:
- 传入合法数字
1时,setState({ number: 1 })生效; - 传入非法字符串
'invalid input'时,onValidNumberInput内的校验!number || typeof number === 'number'不通过,state 保持0。
这种“取回调、手动触发、断言 state”的模式,是浅渲染下测试组件间交互的常用手段,也是.invoke()方法存在的意义——它封装了“取出函数 prop 并调用、随后更新根包装器”的流程。源码中invoke正是基于this.prop(propName)实现的(见 ShallowWrapper.js#L1320-L1332)。
第三步:理解undefined与instance().props的区别
console.log(wrapper.prop('includedProp')); // "Success!" console.log(wrapper.prop('excludedProp')); // undefined console.log(wrapper.instance().props.excludedProp); // "I'm not included"这是整个方法最容易踩坑的地方:excludedProp虽然在渲染时被传给了MyComponent 组件本身,但由于 MyComponent 并未把该 prop 透传到其渲染出的根<div>上,因此:
wrapper.prop('excludedProp')返回undefined(根 DOM 节点上没有该 prop);wrapper.instance().props.excludedProp返回"I'm not included"(组件实例上完整保留了所有传入的 props)。
核心差异:ShallowWrapper 的.prop()与 ReactWrapper 的.prop()
官方文档特别在 NOTE 中强调:
When called on a shallow wrapper,
.prop(key)will return values for props on the root node that the componentrenders, not the component itself. To return the props for the entire React component, usewrapper.instance().props.
这句话道出了浅渲染语义的精髓:
- ShallowWrapper:
wrapper包装的是组件的根渲染节点(即render()返回的最外层元素),因此.prop(key)读取的是渲染结果上的 props,而非传入组件自身的 props; - ReactWrapper:
mount后的wrapper.prop('foo')直接读取组件根节点的 prop(见 docs/api/ReactWrapper/prop.md 的示例:const wrapper = mount(<MyComponent foo={10} />); expect(wrapper.prop('foo')).to.equal(10);)。
这一差异在共享测试套件中有非常明确的对照验证(见 packages/enzyme-test-suite/test/shared/methods/prop.jsx):
class Foo extends React.Component { render() { const { bar, foo } = this.props; return <div className={bar} id={foo} />; } } // 挂载(mount)模式下:读取的是组件自身的 props const wrapper = Wrap(<Foo foo="hi" bar="bye" />); expect(wrapper.prop('foo')).to.equal('hi'); // 传入组件的 props expect(wrapper.prop('className')).to.equal(undefined); // 浅渲染(shallow)模式下:读取的是渲染出的根节点上的 props const wrapperRendered = WrapRendered(<Foo foo="hi" bar="bye" />); expect(wrapperRendered.prop('className')).to.equal('bye'); // 渲染结果上的 props expect(wrapperRendered.prop('foo')).to.equal(undefined); // 组件自身的 props 不在此处测试还验证了.prop()可以在内层节点上使用:
const wrapper = Wrap(( <div className="bax"> <div className="baz" onClick={fn} /> <div className="foo" id="fooId" /> </div> )); expect(wrapper.find('.baz').prop('onClick')).to.equal(fn); expect(wrapper.find('.foo').prop('id')).to.equal('fooId');只要find等操作得到的包装器是单节点,.prop()就能取到该节点的任意 prop(包括函数类型的onClick),这也说明.prop()并非只能用于根节点,而是可以作用于包装器当前指向的任意单节点。
常见使用场景与注意事项
1. 断言组件渲染结果是否正确接收/透传 props
expect(wrapper.prop('className')).to.equal('foo bar');结合hasClass或prop直接断言根节点的 class、id、data-* 等属性,是浅渲染测试中最常见的断言形式。
2. 读取不存在的 prop 时返回undefined
由于propsOfNode对缺失字段不做特殊处理,wrapper.prop('不存在的key')稳定返回undefined,不会抛错。若想断言“组件确实收到了某个 prop 但未透传到渲染结果”,应改用wrapper.instance().props,参见.instance() => ReactComponent。
3. 配合invoke调用函数 prop
当函数 prop 需要被调用且要求调用后同步更新包装器时,优先使用wrapper.invoke(propName)。其实现会先取this.prop(propName),若值不是函数则抛出TypeError: ShallowWrapper::invoke() requires the name of a prop whose value is a function,并在调用后执行this[ROOT].update()刷新根包装器,避免手动update()。
4. 单节点约束
对.find()命中多个元素的结果直接调用.prop()会抛出Method "prop" is meant to be run on 1 node. N found instead.,请先使用.first()或.at(index)收敛。
5. 不要混淆.prop()、.state()与.context()
.prop(key):读取根渲染节点的 props(this.props[key]);.state([key]):读取类组件的 state,仅能在根包装器上对类组件调用(见.state([key]) => Any);.context([key]):读取组件上下文(见.context([key]) => Any)。
三者分别对应 props / state / context 三条数据通路,在浅渲染测试中经常组合使用,用于完整校验组件的数据流。
相关方法
.props() => Object:一次性返回根渲染节点的全部 props 对象,prop(key)等价于props()[key];.state([key]) => Any:读取或按 key 读取类组件 state;.context([key]) => Any:按 key 读取组件 context;.instance() => ReactComponent:获取组件实例,通过wrapper.instance().props读取组件自身收到的全部 props;.invoke(propName) => Any:取出函数 prop 并调用,内部基于prop()实现;.renderProp(propName) => Function:渲染 render-prop 模式下的子内容。
对于完整挂载渲染场景下的 prop 读取语义,可对照阅读 docs/api/ReactWrapper/prop.md,并参考 ShallowWrapper-spec 与 ReactWrapper-spec 中的更多行为验证。
小结
.prop(key)是 ShallowWrapper 中读取节点 props 的最直接入口,它的语义始终围绕“包装器当前指向的渲染节点”展开:
- 必须单节点调用,否则抛错;
- 浅渲染下返回的是组件渲染出的根节点上的 props,而非传入组件自身的 props;后者请用
instance().props; - 对不存在的 key 返回
undefined; - 函数类型的 prop 可以取出后手动调用,或交由
invoke()封装处理。
理解“渲染结果 vs 组件自身”这一差异,是正确使用 ShallowWrapper 进行浅渲染测试的分水岭,也是避免写出“断言永远为真/假”的脆弱测试的关键。
- 测试
- 前端
【免费下载链接】enzyme
JavaScript Testing utilities for React
相关推荐
enzyme ReactWrapper `.context([key])` 详解:读取根组件 React Context 的正确姿势
enzyme ReactWrapper .context key 详解:读取根组件 React Context 的正确姿势 导读 在 enzyme 的 moun
测试前端Enzyme ReactWrapper `.state([key])` 方法完全指南:读取组件 State 的正确姿势
Enzyme ReactWrapper .state key 方法完全指南:读取组件 State 的正确姿势 导读 在基于 Enzyme 编写 React 组件
测试前端Enzyme 中 ShallowWrapper.renderProp() 的完整指南:在浅渲染下测试 Render Prop 组件
Enzyme 中 ShallowWrapper.renderProp 的完整指南:在浅渲染下测试 Render Prop 组件 导读 在 React 测试中,
测试前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考