React学习之扩展Test工具二十九

套路走起

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,documentnavigator 对象)必须是存在的。

(个人测试并没有什么卵用,可能有用,没测试出来)

mockComponent()

mockComponent( componentClass,[mockTagName] )

传递一个虚拟的组件模块给这个方法,给这个组件扩充一些有用的方法,让组件能够被当成一个React组件的仿制品来使用。这个组件将会变成一个简单的<div>(或者是其它标签,如果mockTagName提供了的话),包含任何提供的子节点,而不是像往常一样渲染出来。

isElement()

isElement(element)

如果element是一个任意React元素,则返回true。

isElementOfType()

isElementOfType( element,componentClass )

如果element是一个类型为componentClassReact元素,则返回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;
  }

如果instancecomponentClass的一个实例则返回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 )

在渲染后的树中找出所有组件实例,并且是标签名字符合tagNameDOM组件。

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中的。 */

下一篇将讲ReactAnimation工具

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom