套路走起
import ReactTestUtils from 'react-addons-test-utils' // ES6
var ReactTestUtils = require('react-addons-test-utils') // ES5 with npm
var ReactTestUtils = React.addons.TestUtils; // ES5 with react-with-addons.js
1.概述
ReactTestUtils@H_404_40@对来说,是一个测试react组件的很好框架,在
facebook@H_404_40@中我们使用
Jest@H_404_40@来进行
javascript@H_404_40@的测试,这里我们将讲述怎么通过
React@H_404_40@去测试。
注意
Airbnb@H_404_40@曾经开发出一款基于
React@H_404_40@的测试工具
Enzyme@H_404_40@,这个测试工具用来测试
React@H_404_40@非常不错,如果你决定不用
React@H_404_40@自身提供的测试工具,而是想用其他的,这款测试工具是值得试一试的。
Simulate
renderIntoDocument() mockComponent() isElement() isElementOfType() isDOMComponent() isCompositeComponent() isCompositeComponentWithType() findAllInRenderedTree() scryRenderedDOMComponentsWithClass() findRenderedDOMComponentWithClass() scryRenderedDOMComponentsWithTag() findRenderedDOMComponentWithTag() scryRenderedComponentsWithType() findRenderedComponentWithType()
2.浅呈现(针对虚拟DOM@H_404_40@的测试方式)
浅呈现可以让你的组件只渲染第一层,不渲染所有子组件,如果在浅呈现时进行断言render@H_404_40@方法,那么就会直接返回,不需要去管子组件的行为,因为子组件不会进行实例化和呈现,可以说子组件在浅呈现断言时就相当于没有子组件。
下面是浅呈现的实现方式
createRenderer() shallowRenderer.render() shallowRenderer.getRenderOutput()
createRenderer()@H_404_40@
这个函数会在你的测试中创建一个浅呈现,你可以用它来代替平时render@H_404_40@渲染到视图中的操作,然后进行测试,从而可以提取出组件的输出。
shallowRenderer.render()@H_404_40@
这个函数类似于ReactDOM.render()@H_404_40@,但是通过它并不会加入到
DOM@H_404_40@中,而仅仅只是渲染一层的深度,也就是说不会处理子组件,这样我们可以通过后续的
shallowRenderer.getRenderOutput()@H_404_40@函数来分离出子组件
shallowRenderer.getRenderOutput()@H_404_40@
在shallowRenderer.render()@H_404_40@或者
createRenderer()@H_404_40@ 创建的
render@H_404_40@调用后,你可以使用这个函数,进行浅呈现的输出。
到这里你或许会觉得,这都写的什么鬼,不用着急,请看例子
这是一个要呈现的函数式组件
function MyComponent() {
return (
<div>
<span className="heading">Title</span>
<Subcomponent foo="bar" />
</div>
);
}
断言测试
const renderer = ReactTestUtils.createRenderer();
renderer.render(<MyComponent />);
const result = renderer.getRenderOutput();
//下面代码请在nodejs环境下测试
expect(result.type).toBe('div');
expect(result.props.children).toEqual([
<span className="heading">Title</span>,<Subcomponent foo="bar" />
]);
当然浅展现测试存在一定程度上的局限性。
3.函数详解
Simulate@H_404_40@对象
Simulate.{eventName}(
element,[eventData]
)
Simulate@H_404_40@(模拟事件)在
DOM@H_404_40@节点上派发,附带可选的
eventData@H_404_40@事件数据。这可能是在
ReactTestUtils@H_404_40@中最有用的工具。
Simulate@H_404_40@为每一个事件都提供了一个方法用来模拟该事件。
点击元素
// <button ref="button">...</button>
const node = this.refs.button;
ReactTestUtils.Simulate.click(node);
改变元素和按键事件
// <input ref="input" />
const node = this.refs.input;
node.value = 'giraffe';
ReactTestUtils.Simulate.change(node);
ReactTestUtils.Simulate.keyDown(node,{key: "Enter",keyCode: 13,which: 13});
其他事件react@H_404_40@官网上也有
该注意的是,因为你是模拟事件,所以你要提供所有事件产生的数据。
renderIntoDocument()@H_404_40@
renderIntoDocument(element)
把一个组件渲染成一个在文档中分离的DOM@H_404_40@节点(即将组件渲染成一个
DOM@H_404_40@节点但是不将这个节点插入到视图中),返回一个
DOM@H_404_40@节点。
注意
此方法要求存在一个真实的DOM@H_404_40@环境,否则会报错。因此,测试用例之中,
DOM@H_404_40@环境(即
window@H_404_40@,
document@H_404_40@ 和
navigator@H_404_40@ 对象)必须是存在的。
(个人测试并没有什么卵用,可能有用,没测试出来)
mockComponent()@H_404_40@
mockComponent( componentClass,[mockTagName] )
传递一个虚拟的组件模块给这个方法,给这个组件扩充一些有用的方法,让组件能够被当成一个React@H_404_40@组件的仿制品来使用。这个组件将会变成一个简单的
<div>@H_404_40@(或者是其它标签,如果
mockTagName@H_404_40@提供了的话),包含任何提供的子节点,而不是像往常一样渲染出来。
isElement()@H_404_40@
isElement(element)
如果element是一个任意React元素,则返回true。
isElementOfType()@H_404_40@
isElementOfType( element,componentClass )
如果element@H_404_40@是一个类型为
componentClass@H_404_40@的
React@H_404_40@元素,则返回
true@H_404_40@。
isDOMComponent()@H_404_40@
isDOMComponent(instance)
//源码
isDOMComponent: function (inst) {
return !!(inst && inst.nodeType === 1 && inst.tagName);
}
如果是一个DOM@H_404_40@组件(例如
<div>@H_404_40@或者
<span>@H_404_40@),则返回
true@H_404_40@。
isCompositeComponent()@H_404_40@
isCompositeComponent(instance)
//源码
isCompositeComponent: function (inst) {
if (ReactTestUtils.isDOMComponent(inst)) {
return false;
}
return inst != null && typeof inst.render === 'function' && typeof inst.setState === 'function';
}
isCompositeComponentWithType()@H_404_40@
isCompositeComponentWithType(
instance,componentClass
)
//源码
isCompositeComponentWithType: function (inst,type) { if (!ReactTestUtils.isCompositeComponent(inst)) { return false; } var internalInstance = ReactInstanceMap.get(inst);
var constructor = internalInstance._currentElement.type;
return constructor === type;
}
如果instance@H_404_40@是
componentClass@H_404_40@的一个实例则返回
true@H_404_40@
findAllInRenderedTree()@H_404_40@
findAllInRenderedTree(
tree,test//这是个函数
)
//源码
findAllInRenderedTree: function (inst,test) {
if (!inst) {
return [];
}
!ReactTestUtils.isCompositeComponent(inst) ? "development" !== 'production' ? invariant(false,'findAllInRenderedTree(...): instance must be a composite component') : _prodInvariant('10') : void 0;
return findAllInRenderedTreeInternal(ReactInstanceMap.get(inst),test);
}
遍历tree@H_404_40@中所有组件,收集
test(component)@H_404_40@返回
true@H_404_40@的所有组件。就这个本身来说不是很有用,但是它可以为其它测试提供原始数据。
scryRenderedDOMComponentsWithClass()@H_404_40@
scryRenderedDOMComponentsWithClass(
tree,className
)
//源码
scryRenderedDOMComponentsWithClass: function (root,classNames) {
return ReactTestUtils.findAllInRenderedTree(root,function (inst) {
if (ReactTestUtils.isDOMComponent(inst)) {
var className = inst.className;
if (typeof className !== 'string') {
// SVG,probably.
className = inst.getAttribute('class') || '';
}
var classList = className.split(/\s+/);
if (!Array.isArray(classNames)) {
!(classNames !== undefined) ? "development" !== 'production' ? invariant(false,'TestUtils.scryRenderedDOMComponentsWithClass expects a className as a second argument.') : _prodInvariant('11') : void 0;
classNames = classNames.split(/\s+/);
}
return classNames.every(function (name) {
return classList.indexOf(name) !== -1;
});
}
return false;
});
}
查找组件的所有实例,这些实例都在渲染后的树中,并且是带有className@H_404_40@类名的
DOM@H_404_40@组件。
findRenderedDOMComponentWithClass()@H_404_40@
findRenderedDOMComponentWithClass( tree,className )
类似于scryRenderedDOMComponentsWithClass()@H_404_40@,但是它只返回一个结果,如果有其它满足条件的,则会抛出异常。
scryRenderedDOMComponentsWithTag()@H_404_40@
scryRenderedDOMComponentsWithTag( tree,tagName )
在渲染后的树中找出所有组件实例,并且是标签名字符合tagName@H_404_40@的
DOM@H_404_40@组件。
findRenderedDOMComponentWithTag@H_404_40@
findRenderedDOMComponentWithTag( tree,tagName )
类似于scryRenderedDOMComponentsWithTag()@H_404_40@,但是它只返回一个结果,如果有其它满足条件的,则会抛出异常。
scryRenderedComponentsWithType@H_404_40@
scryRenderedComponentsWithType( tree,componentClass )
找出所有组件实例,这些组件的类型为componentClass@H_404_40@
findRenderedComponentWithType()@H_404_40@
findRenderedComponentWithType( tree,componentClass )
类似于scryRenderedComponentsWithType()@H_404_40@,但是它只返回一个结果,如果有其它满足条件的,则会抛出异常。
这里需要注意的是,我把大部分函数的实现源码都展现出来了,这里大家需要注意一个问题,我们用的组件类和最终形成的组件是不同的,也就是说的React@H_404_40@组件元素并不是我们的组件的实例.
如下:
class Tmq extends React.Component{
constructor(props){
super(props);
}
render(){
return (<MyComponent/>);
}
}
console.log(TestUtils.isCompositeComponentWithType(<Tmq/>,Tmq));
//返回false
/*通过源码我们也知道isCompositeComponentWithType的判断方式,而Tmq这个对象根本没有render和setState函数,所以很明显<Tmq/>和组件类根本是两个玩意,<Tmq/>是通过React.createElement创建的,两者不能混为一谈*/
下面的代码就会返回true
class Tmq extends React.Component{
constructor(props){
super(props);
}
render(){
return (<MyComponent/>);
}
}
class Tmq extends React.Component{
constructor(props){
super(props);
}
render(){
console.log(TestUtils.isCompositeComponent(this,Tmq))
return (<MyComponent/>);
}
}
ReactDOM.render(
<Tmq/>,document.getElementById('example')
);
/* 由此我们可以推断出一些东西出来: 首先,<Tmq />并不是直接用组件类实例化出来的,它经过了React.createElement来处理。 然后调用ReactDOM.render()函数时,才会调用render进行渲染所以,组件类的实例化部分是进行在ReactDOM.render中的。 */