Test Utilities

如何引入

  1. import ReactTestUtils from 'react-dom/test-utils'; // ES6
  2. var ReactTestUtils = require('react-dom/test-utils'); // ES5 使用 npm 的方式

概览

ReactTestUtils 可搭配你所选的测试框架,轻松实现 React 组件测试。在 Facebook 内部,我们使用 Jest 来轻松实现 JavaScript 测试。你可以从 Jest 官网的 React 教程中了解如何开始使用它。

注意:

我们推荐使用 React Testing Library,它使得针对组件编写测试用例就像终端用户在使用它一样方便。

另外,Airbnb 发布了一款叫作 Enzyme 的测试工具,通过它能够轻松对 React 组件的输出进行断言、操控和遍历。

参考

act()

为断言准备一个组件,包裹要渲染的代码并在调用 act() 时执行更新。这会使得测试更接近 React 在浏览器中的工作方式。

注意

如果你使用了 react-test-renderer,它也提供了与 act 行为相同的函数。

例如,假设我们有个 Counter 组件:

  1. class Counter extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {count: 0};
  5. this.handleClick = this.handleClick.bind(this);
  6. }
  7. componentDidMount() {
  8. document.title = `You clicked ${this.state.count} times`;
  9. }
  10. componentDidUpdate() {
  11. document.title = `You clicked ${this.state.count} times`;
  12. }
  13. handleClick() {
  14. this.setState(state => ({
  15. count: state.count + 1,
  16. }));
  17. }
  18. render() {
  19. return (
  20. <div>
  21. <p>You clicked {this.state.count} times</p>
  22. <button onClick={this.handleClick}>
  23. Click me
  24. </button>
  25. </div>
  26. );
  27. }
  28. }

以下是其测试代码:

  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import { act } from 'react-dom/test-utils';
  4. import Counter from './Counter';
  5. let container;
  6. beforeEach(() => {
  7. container = document.createElement('div');
  8. document.body.appendChild(container);
  9. });
  10. afterEach(() => {
  11. document.body.removeChild(container);
  12. container = null;
  13. });
  14. it('can render and update a counter', () => {
  15. // 首先测试 render 和 componentDidMount
  16. act(() => {
  17. ReactDOM.render(<Counter />, container);
  18. });
  19. const button = container.querySelector('button');
  20. const label = container.querySelector('p');
  21. expect(label.textContent).toBe('You clicked 0 times');
  22. expect(document.title).toBe('You clicked 0 times');
  23. // 再测试 render 和 componentDidUpdate
  24. act(() => {
  25. button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
  26. });
  27. expect(label.textContent).toBe('You clicked 1 times');
  28. expect(document.title).toBe('You clicked 1 times');
  29. });

千万不要忘记,只有将 DOM 容器添加到 document 时,触发 DOM 事件才生效。你可以使用类似于 React Testing Library 等库来减少样板代码(boilerplate code)。

  • recipes 文档包含了关于 act() 函数的示例、用法及更多详细信息。

mockComponent()

  1. mockComponent(
  2. componentClass,
  3. [mockTagName]
  4. )

将模拟组件模块传入这个方法后,React 内部会使用有效的方法填充该模块,使其成为虚拟的 React 组件。与通常的渲染不同,组件将变成一个简单的 <div> (如果提供了 mockTagName 则是其他标签),包含任何提供的子级。

注意:

mockComponent() 是一个过时的 API,我们推荐使用 jest.mock() 来代替。


isElement()

  1. isElement(element)

element 是任何一种 React 元素时,返回 true


isElementOfType()

  1. isElementOfType(
  2. element,
  3. componentClass
  4. )

element 是一种 React 元素,并且它的类型是参数 componentClass 的类型时,返回 true


isDOMComponent()

  1. isDOMComponent(instance)

instance 是一个 DOM 组件(比如 <div><span>)时,返回 true


isCompositeComponent()

  1. isCompositeComponent(instance)

instance 是一个用户自定义的组件,比如一个类或者一个函数时,返回 true


isCompositeComponentWithType()

  1. isCompositeComponentWithType(
  2. instance,
  3. componentClass
  4. )

instance 是一个组件,并且它的类型是参数 componentClass 的类型时,返回 true


findAllInRenderedTree()

  1. findAllInRenderedTree(
  2. tree,
  3. test
  4. )

遍历所有在参数 tree 中的组件,记录所有 test(component)true 的组件。单独调用此方法不是很有用,但是它常常被作为底层 API 被其他测试方法使用。


scryRenderedDOMComponentsWithClass()

  1. scryRenderedDOMComponentsWithClass(
  2. tree,
  3. className
  4. )

查找渲染树中组件的所有 DOM 元素,这些组件是 css 类名与参数 className 匹配的 DOM 组件。


findRenderedDOMComponentWithClass()

  1. findRenderedDOMComponentWithClass(
  2. tree,
  3. className
  4. )

用法与 scryRenderedDOMComponentsWithClass() 保持一致,但期望仅返回一个结果。不符合预期的情况下会抛出异常。


scryRenderedDOMComponentsWithTag()

  1. scryRenderedDOMComponentsWithTag(
  2. tree,
  3. tagName
  4. )

查找渲染树中组件的所有的 DOM 元素,这些组件是标记名与参数 tagName 匹配的 DOM 组件。


findRenderedDOMComponentWithTag()

  1. findRenderedDOMComponentWithTag(
  2. tree,
  3. tagName
  4. )

用法与 scryRenderedDOMComponentsWithTag() 保持一致,但期望仅返回一个结果。不符合预期的情况下会抛出异常。


scryRenderedComponentsWithType()

  1. scryRenderedComponentsWithType(
  2. tree,
  3. componentClass
  4. )

查找组件类型等于 componentClass 组件的所有实例。


findRenderedComponentWithType()

  1. findRenderedComponentWithType(
  2. tree,
  3. componentClass
  4. )

用法与 scryRenderedComponentsWithType() 保持一致,但期望仅返回一个结果。不符合预期的情况下会抛出异常。


renderIntoDocument()

  1. renderIntoDocument(element)

渲染 React 元素到 document 中的某个单独的 DOM 节点上。这个函数需要一个 DOM 对象。 它实际相当于:

  1. const domContainer = document.createElement('div');
  2. ReactDOM.render(element, domContainer);

注意:

你需要在引入 React 之前确保 window 存在,window.documentwindow.document.createElement 能在全局环境中获取到。不然 React 会认为它没有权限去操作 DOM,以及像 setState 这样的方法将不可用。


其他工具方法

Simulate

  1. Simulate.{eventName}(
  2. element,
  3. [eventData]
  4. )

使用可选的 eventData 事件数据来模拟在 DOM 节点上触发事件。

React 所支持的所有事件Simulate 中都有对应的方法。

点击元素

  1. // <button ref={(node) => this.button = node}>...</button>
  2. const node = this.button;
  3. ReactTestUtils.Simulate.click(node);

修改一个 input 输入框的值,然后按回车键。

  1. // <input ref={(node) => this.textInput = node} />
  2. const node = this.textInput;
  3. node.value = 'giraffe';
  4. ReactTestUtils.Simulate.change(node);
  5. ReactTestUtils.Simulate.keyDown(node, {key: "Enter", keyCode: 13, which: 13});

注意:

你必须提供一切需要在组件中用到的事件属性(比如:keyCode、which 等等),因为 React 没有为你创建这些属性。