fullstact react 学习笔记unit testing (2)Enzyme

1、Enzyme的在测试React Component时的好处

(1)独立测试组件。比如测试父组件,如果子组件发生了变动,父组件的测试依然是通过的。

(2)不是测试真实的DOM,而是在虚拟DOM的基础上进行测试,因此速度比较快。

2、使用create-react-app中提供的test功能

使用npm test我们便可以对App进行测试,测试完成后并不会直接退出,而是会处于观察模式,每当代码发生变化时,变回重新进行测试。

我们仅需要保留一个测试窗口处于打开状态。

3、Enzyme的安装

(1)在cerate-react-app生成的react-scripts中并不包含Enzyme,因此我们需要在package.json中配置Enzyme.

(2)因为Enzyme是在react-test-renderer基础上开发的,因此我们也需要引入react-test-renderer。

(3)为Enzyme增加一个转换器adapter,Enzym为每个react配置了一个adapter,选择react对应的版本。

修改后的package.jason文件如下:

  "devDependencies": {
    "enzyme": "3.1.0",
    "enzyme-adapter-react-16": "1.0.1",
    "react-scripts": "1.0.14",
    "react-test-renderer": "16.0.0"
  },

  

4、Enzyme测试使用的组件App.js

import React from 'react';

class App extends React.Component {
  state = {
    items: [],
    item: '',
  };

  onItemChange = (e) => {
    this.setState({
      item: e.target.value,
    });
  };

  addItem = (e) => {
    e.preventDefault();

    this.setState({
      items: this.state.items.concat(
        this.state.item
      ),
      item: '',
    });
  };

  render() {
    const submitDisabled = !this.state.item;
    return(
      <div
        className='ui text container'
        id='app'
      >
        <table className='ui selectable structured large table'>
          <thead>
            <tr>
              <th>Items</th>
            </tr>
          </thead>
          <tbody>
            {
              this.state.items.map((item, idx) => (
                <tr
                  key={idx}
                >
                  <td>{item}</td>
                </tr>
              ))
            }
          </tbody>
          <tfoot>
            <tr>
              <th>
                <form
                  className='ui form'
                  onSubmit={this.addItem}
                >
                <div className='field'>
                  <input
                    className='prompt'
                    type='text'
                    placeholder='Add item...'
                    value={this.state.item}
                    onChange={this.onItemChange}
                  />
                </div>
                <button
                  className='ui button'
                  type='submit'
                  disabled={submitDisabled}
                >
                  Add item
                </button>
                </form>
              </th>
            </tr>
          </tfoot>
        </table>
      </div>
    )
  }
}

export default App;

  

5、Enzyme的使用

(1)声明Enzyme使用的装换器

import Enzyme  from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({ adapter: new Adapter() });

  如果我们在每个测试中引入上述语句,随着组件测试的增多,每次都要复制和粘贴,我们可以在setupTest.js中配置上述引入,这样在每次进行测试时便会自动引入上述模块

完整示例如下:

import raf from './tempPolyfills'

import Enzyme  from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({ adapter: new Adapter() });

  tempPolyfills是干什么用的?它其实是在为react 16打个补丁。

(2)为react 16打个补丁

由于react 16依赖一个浏览器api 名字为requestAnimationFrame,但是在测试react 时候requestAnimationFrame并不会被自动引入。由此导致测试react失败,未来的react版本将会解决这个问题。但是现在我们还需要手工引入它。

在tempPolyfills.js中

const raf = global.requestAnimationFrame = (cb) => {
  setTimeout(cb, 0);
};
  
export default raf;

  

(3)测试一个基础组件

a、引入要测试的Component

import App from './App';

  

b、引入React和shallow

import React from 'react';
import { shallow } from 'enzyme';

  在这里我们仅用到了enzyme的shallow方法 ,因此我们显示声明一下。

c、开始测试组件

第一步:使用shallow生成shallowWrapper,包含有虚拟的DOM

第二步:使用ShallowWrapper提供的遍历和筛选方法找到我们要测试的组件内容,并进行测试该内容已经render到页面上。

第三步:模拟动作交互,测试响应的正确性。

(1)使用contains:严格包含

import App from './App'
import React from 'react'
import {shallow} from 'enzyme'

describe('App',()=>{
  // 测试是否生成的table的表头th是否有items
  it('测试是否生成的table的表头th是否有items',()=>{
    // 首先,使用shallow生成shallowwrapper,包含了虚拟的DOM
    const wrapper=shallow(<App />);
    //shallowwrapper给我们提供了遍历和筛选DOM的功能,如contains
    expect(wrapper.contains(<th>Items</th>)).toBe(true);
  });
});
(2)使用containsMatchingElement(),近似包含
由于contains测试时包含了标签所有的属性,这里我们仅仅测试组件而忽略属性,可以使用containsMatchingElement()
  it('测试是否含有<button>addItem</button>',()=>{
    // 由于contains测试时包含了标签所有的属性,这里我们仅仅测试组件而忽略属性,可以使用containsMatchingElement()
    const wrapper=shallow(<App />);
    //shallowwrapper给我们提供了遍历和筛选DOM的功能,如contains
    expect(wrapper.containsMatchingElement(<button>Add item</button>)).toBe(true);
  });
});

(3)使用find遍历查找,生成一个shallowwrpper对象array 

使用find返回一个shallowwrapper,类似于array拥有length属性,first()返回第一个shallowwrapper.

shallowwrapper的props方法返回一个属性对象
it('测试button的disabled属性是否为true',()=>{
    // 由于contains测试时包含了标签所有的属性,这里我们仅仅测试组件而忽略属性,可以使用containsMatchingElement()
    const wrapper=shallow(<App />);
    // find返回一个shallowwrapper,类似于array拥有length属性,first()返回第一个shallowwrapper.
    const button = wrapper.find('button').first();
    //shallowwrapper的props方法返回一个属性对象
    expect(button.props().disabled).toBe(true);
  });

(4)使用beforeEach

上面代码中我们每一个都声明了相同的变量wrapper,我们可以将wrapper放在所有的it之前,describe之后,这样所有的it中便可以引用相同的wrapper了,免去重复声明的问题。

但是直接声明wrapper会遇到一个问题,如果在一个测试中修改了component,这样在该测试下面测试中,都将使用修改后的wrapper进行测试,这会导致测试的不可预知性。

因此,在每个测试前重新的render一下,保证所测试的组件拿到的wrapper都是一致的,最新的,这样就不会导致预测的不可知性。

beforeEach就是来干这个事情的,每次测试前都会重新的render一次,生成一个新的wrapper

示例代码如下:

let wrapper;

  beforeEach(() => {
    wrapper = shallow(
      <App />
    );
  });

  

(5)使用simulate()模拟用户的操作

a、模拟用户输入

重新定义一个describe,以便于其他的展示组件测试分开。

describe('测试用户输入',()=>{
    const item='Vancouver';
    beforeEach(()=>{
      const input = wrapper.find('input').first()
      // 模拟用户输入
      input.simulate('change',{
        target:{value:item}
      })
    });
    it('测试当用户输入时是否更新state中的item',()=>{
      expect(wrapper.state().item).toBe(item);
    });
    it('测试当用户输入时提交框的disabled属性是否变为false',()=>{
      const button = wrapper.find('button').first()
      expect(button.props().disabled).toBe(false);
    });
  });

  b、模拟用户清楚

describe('用户clear输入的内容',()=>{
      beforeEach(()=>{
        const input = wrapper.find('input').first()
        // 模拟用户清楚内容
        input.simulate('change',{
          target:{value:''}
        })
      });
      it('测试当用户清楚内容时是否更新state中的item',()=>{
        expect(wrapper.state().item).toBe('');
      });
      it('测试当用户清楚内容时提交框的disabled属性是否变为false',()=>{
        const button = wrapper.find('button').first()
        expect(button.props().disabled).toBe(true);
      });
    });

  c、模拟用户提交

describe('用户点击提交时',()=>{
      // items中增加一条记录
      //table中存在一条记录
      // input框清空,
      // submit的disabeled属性变为true

      beforeEach(()=>{
        const form = wrapper.find('form').first()
        // 模拟用户清楚内容
        form.simulate('submit',{
          preventDefault:()=>{},
        });
      });
      it('state中的items增加一条记录',()=>{
        expect(wrapper.state().items).toContain(item);
      });
      it('table中含有一条记录',()=>{
        expect(wrapper.containsMatchingElement(<td>{item}</td>)).toBe(true);
      });
      it('input框被清空',()=>{
        const input = wrapper.find('input').first();
        expect(input.props().value).toEqual('');
      });
      it('测试当用户提交后提交框的disabled属性是否变为true',()=>{
        const button = wrapper.find('button').first()
        expect(button.props().disabled).toBe(true);
      });
    });

  通过模拟用户输入、清楚和提交我们发现要不断的声明input 和button,是否也可以将其增加到beforeEach中,以便于减少重复的声明,但这并不是可行的,因为beforeEach每次都是render最新的DOM,而我们模拟用户输入需要的input和button是在修改了DOM之后的input和button。

posted @ 2018-08-18 18:51  tutu_python  阅读(245)  评论(0)    收藏  举报