套路走起
- 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
工具