Test Utilities
如何 Import
import ReactTestUtils from 'react-dom/test-utils'; // ES6
var ReactTestUtils = require('react-dom/test-utils'); // ES5 with npm
概觀
ReactTestUtils
使你可以輕鬆在你選擇的測試框架中測試 React component。在 Facebook,我們使用 Jest 以方便地進行 JavaScript 測試。你可以從 Jest 網站的 React 教學學習如何使用 Jest。
注意:
我們推薦使用 React Testing Library,它促使你寫出的測試能像使用者一樣地使用 component。
對於 React <= 16 的版本,Enzyme 的測試工具,讓你能輕易 assert、操作及遍歷 React component 的輸出。
act()
mockComponent()
isElement()
isElementOfType()
isDOMComponent()
isCompositeComponent()
isCompositeComponentWithType()
findAllInRenderedTree()
scryRenderedDOMComponentsWithClass()
findRenderedDOMComponentWithClass()
scryRenderedDOMComponentsWithTag()
findRenderedDOMComponentWithTag()
scryRenderedComponentsWithType()
findRenderedComponentWithType()
renderIntoDocument()
Simulate
參考資料
act()
為了準備讓 component 進行 assert,將 render component 及執行更新的程式碼放在 act()
中。這讓你的測試更貼近 React 在瀏覽器中的運作方式。
注意
如果你使用
react-test-renderer
,它也提供行為相同的act
function。
舉例來說,假設我們有個 Counter
component:
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {count: 0};
this.handleClick = this.handleClick.bind(this);
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
handleClick() {
this.setState(state => ({
count: state.count + 1,
}));
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={this.handleClick}>
Click me
</button>
</div>
);
}
}
我們可以這樣測試:
import React from 'react';
import ReactDOM from 'react-dom/client';
import { act } from 'react-dom/test-utils';import Counter from './Counter';
let container;
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
document.body.removeChild(container);
container = null;
});
it('can render and update a counter', () => {
// Test first render and componentDidMount
act(() => { ReactDOM.createRoot(container).render(<Counter />); }); const button = container.querySelector('button');
const label = container.querySelector('p');
expect(label.textContent).toBe('You clicked 0 times');
expect(document.title).toBe('You clicked 0 times');
// Test second render and componentDidUpdate
act(() => { button.dispatchEvent(new MouseEvent('click', {bubbles: true})); }); expect(label.textContent).toBe('You clicked 1 times');
expect(document.title).toBe('You clicked 1 times');
});
- 不要忘記,只有在 DOM container 已加到
document
裡面時,才可以 dispatch DOM event。你可以使用如react-testing-library
的 helper 來減少 boilerplate 程式碼。 recipes
說明文件內包含了act()
的詳細資訊,包含範例以及用法。
mockComponent()
mockComponent(
componentClass,
[mockTagName]
)
傳遞一個被 mock 的 component module 到這個方法後,它會增加有用的方法,讓它能做為虛擬的 React component。component 不會像平常一樣 render,它會變成一個簡單的 <div>
(或其他標籤,如果有提供 mockTagName
),包含任何提供的 children。
注意:
mockComponent()
是 legacy API。我們建議以jest.mock()
作為替代。
isElement()
isElement(element)
如果 element
是 React element 的話就回傳 true
。
isElementOfType()
isElementOfType(
element,
componentClass
)
如果 element
是 type 為 componentClass
的 React element 就回傳 true
。
isDOMComponent()
isDOMComponent(instance)
如果 instance
是 DOM component(如 <div>
或 <span>
)就回傳 true
。
isCompositeComponent()
isCompositeComponent(instance)
如果 instance
是使用者定義的 component,例如 class 或 function,就回傳 true
。
isCompositeComponentWithType()
isCompositeComponentWithType(
instance,
componentClass
)
如果 instance
是 type 為 componentClass
的 component 就回傳 true
。
findAllInRenderedTree()
findAllInRenderedTree(
tree,
test
)
遍歷 tree
中的所有 component,並收集 test(component)
為 true
的所有 component。這個方法本身不是那麼好用,但是它被其他測試工具做為基礎使用。
scryRenderedDOMComponentsWithClass()
scryRenderedDOMComponentsWithClass(
tree,
className
)
在已經被 render 的 tree 中尋找所有 DOM element,回傳 class 名稱符合 className
的 DOM component。
findRenderedDOMComponentWithClass()
findRenderedDOMComponentWithClass(
tree,
className
)
與 scryRenderedDOMComponentsWithClass()
相似,不過預期只有一個結果。如果符合預期則回傳那個結果,否則拋出例外。
scryRenderedDOMComponentsWithTag()
scryRenderedDOMComponentsWithTag(
tree,
tagName
)
在已經被 render 的 tree 中尋找所有 DOM element,回傳 tag 名稱符合 tagName
的 DOM component。
findRenderedDOMComponentWithTag()
findRenderedDOMComponentWithTag(
tree,
tagName
)
與 scryRenderedDOMComponentsWithTag()
相似,不過預期只有一個結果。如果符合預期則回傳那個結果,否則拋出例外。
scryRenderedComponentsWithType()
scryRenderedComponentsWithType(
tree,
componentClass
)
尋找所有 component type 與 componentClass
相同的 instance。
findRenderedComponentWithType()
findRenderedComponentWithType(
tree,
componentClass
)
與 scryRenderedComponentsWithType()
相似,不過預期只有一個結果。如果符合預期則回傳那個結果,否則拋出例外。
renderIntoDocument()
renderIntoDocument(element)
Render React element 到 document 中獨立的 DOM node 裡。這個 function 需要 DOM。它等效於:
const domContainer = document.createElement('div');
ReactDOM.createRoot(domContainer).render(element);
注意:
在 import
React
前,你需要讓window
、window.document
和window.document.createElement
在全域可以使用。否則 React 會認為它無法存取 DOM,像setState
之類的方法也將無法運作。
其他工具
Simulate
Simulate.{eventName}(
element,
[eventData]
)
在 DOM node 上用可選的 eventData
事件資料模擬 event dispatch。
每一個 React 支援的事件在 Simulate
都有對應的方法。
點擊 element
// <button ref={(node) => this.button = node}>...</button>
const node = this.button;
ReactTestUtils.Simulate.click(node);
更改輸入欄位的值,然後按 ENTER 鍵。
// <input ref={(node) => this.textInput = node} />
const node = this.textInput;
node.value = 'giraffe';
ReactTestUtils.Simulate.change(node);
ReactTestUtils.Simulate.keyDown(node, {key: "Enter", keyCode: 13, which: 13});
注意:
你需要提供所有在你的 component 中有使用的事件屬性(如 keyCode、which 等等),因為 React 不會為你建立這些東西。