套路走起
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
对来说,是一个测试react组件的很好框架,在facebook
中我们使用Jest
来进行javascript
的测试,这里我们将讲述怎么通过React
去测试。
注意
Airbnb
曾经开发出一款基于React
的测试工具Enzyme
,这个测试工具用来测试React
非常不错,如果你决定不用React
自身提供的测试工具,而是想用其他的,这款测试工具是值得试一试的。
React
自身测试工具设计的函数
Simulate
renderIntoDocument() mockComponent() isElement() isElementOfType() isDOMComponent() isCompositeComponent() isCompositeComponentWithType() findAllInRenderedTree() scryRenderedDOMComponentsWithClass() findRenderedDOMComponentWithClass() scryRenderedDOMComponentsWithTag() findRenderedDOMComponentWithTag() scryRenderedComponentsWithType() findRenderedComponentWithType()
2.浅呈现(针对虚拟DOM
的测试方式)
浅呈现可以让你的组件只渲染第一层,不渲染所有子组件,如果在浅呈现时进行断言render
方法,那么就会直接返回,不需要去管子组件的行为,因为子组件不会进行实例化和呈现,可以说子组件在浅呈现断言时就相当于没有子组件。
下面是浅呈现的实现方式
createRenderer() shallowRenderer.render() shallowRenderer.getRenderOutput()
createRenderer()
这个函数会在你的测试中创建一个浅呈现,你可以用它来代替平时render
渲染到视图中的操作,然后进行测试,从而可以提取出组件的输出。
shallowRenderer.render()
这个函数类似于ReactDOM.render()
,但是通过它并不会加入到DOM
中,而仅仅只是渲染一层的深度,也就是说不会处理子组件,这样我们可以通过后续的shallowRenderer.getRenderOutput()
函数来分离出子组件
shallowRenderer.getRenderOutput()
在shallowRenderer.render()
或者createRenderer()
创建的render
调用后,你可以使用这个函数,进行浅呈现的输出。
到这里你或许会觉得,这都写的什么鬼,不用着急,请看例子
这是一个要呈现的函数式组件
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
对象
Simulate.{eventName}(
element,[eventData]
)
Simulate
(模拟事件)在DOM
节点上派发,附带可选的eventData
事件数据。这可能是在ReactTestUtils
中最有用的工具。
Simulate
为每一个事件都提供了一个方法用来模拟该事件。
点击元素
// <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
官网上也有
该注意的是,因为你是模拟事件,所以你要提供所有事件产生的数据。
renderIntoDocument()
renderIntoDocument(element)
把一个组件渲染成一个在文档中分离的DOM
节点(即将组件渲染成一个DOM
节点但是不将这个节点插入到视图中),返回一个DOM
节点。
注意
此方法要求存在一个真实的DOM
环境,否则会报错。因此,测试用例之中,DOM
环境(即window
,document
和 navigator
对象)必须是存在的。
(个人测试并没有什么卵用,可能有用,没测试出来)
mockComponent()
mockComponent( componentClass,[mockTagName] )
传递一个虚拟的组件模块给这个方法,给这个组件扩充一些有用的方法,让组件能够被当成一个React
组件的仿制品来使用。这个组件将会变成一个简单的<div>
(或者是其它标签,如果mockTagName
提供了的话),包含任何提供的子节点,而不是像往常一样渲染出来。
isElement()
isElement(element)
如果element是一个任意React元素,则返回true。
isElementOfType()
isElementOfType( element,componentClass )
如果element
是一个类型为componentClass
的React
元素,则返回true
。
isDOMComponent()
isDOMComponent(instance)
//源码
isDOMComponent: function (inst) {
return !!(inst && inst.nodeType === 1 && inst.tagName);
}
如果是一个DOM
组件(例如<div>
或者<span>
),则返回true
。
isCompositeComponent()
isCompositeComponent(instance)
//源码
isCompositeComponent: function (inst) {
if (ReactTestUtils.isDOMComponent(inst)) {
return false;
}
return inst != null && typeof inst.render === 'function' && typeof inst.setState === 'function';
}
isCompositeComponentWithType()
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
是componentClass
的一个实例则返回true
findAllInRenderedTree()
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
中所有组件,收集test(component)
返回true
的所有组件。就这个本身来说不是很有用,但是它可以为其它测试提供原始数据。
scryRenderedDOMComponentsWithClass()
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
类名的DOM
组件。
findRenderedDOMComponentWithClass()
findRenderedDOMComponentWithClass( tree,className )
类似于scryRenderedDOMComponentsWithClass()
,但是它只返回一个结果,如果有其它满足条件的,则会抛出异常。
scryRenderedDOMComponentsWithTag()
scryRenderedDOMComponentsWithTag( tree,tagName )
在渲染后的树中找出所有组件实例,并且是标签名字符合tagName
的DOM
组件。
findRenderedDOMComponentWithTag
findRenderedDOMComponentWithTag( tree,tagName )
类似于scryRenderedDOMComponentsWithTag()
,但是它只返回一个结果,如果有其它满足条件的,则会抛出异常。
scryRenderedComponentsWithType
scryRenderedComponentsWithType( tree,componentClass )
找出所有组件实例,这些组件的类型为componentClass
findRenderedComponentWithType()
findRenderedComponentWithType( tree,componentClass )
类似于scryRenderedComponentsWithType()
,但是它只返回一个结果,如果有其它满足条件的,则会抛出异常。
这里需要注意的是,我把大部分函数的实现源码都展现出来了,这里大家需要注意一个问题,我们用的组件类和最终形成的组件是不同的,也就是说的React
组件元素并不是我们的组件的实例.
如下:
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中的。 */
下一篇将讲
React
中Animation
工具