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