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工具

發佈了447 篇原創文章 · 獲贊 471 · 訪問量 51萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章