组件单元测试

367 阅读4分钟

为什么要写单元测试

测试是检查代码的代码,能够大大增强我们对应用的信心。更重要的是,测试会阻止你在修复一个bug的时候的同时其他地方带来新的bug,让我们能够放开手脚进行功能的添加与大规模重构。

安装

yarn add jest

配置package.json

"scripts": {
  "test": "jest",
},

测一个函数方法

1.写一个math.js

function add(a, b) {
  return a+b;
}

function minus(a, b) {
  return a-b;
}

module.exports = {
  add,
  minus
}

2.在src目录下新建一个__test__目录存放所有模块的测试用例

cd src
mkdir __test__

3.新建一个单元测试,测试math.js里面的方法

touch math.test.js

4.编写测试内容

import math from '../math';

// describe用来表示一组相关的测试
describe('测试add方法', () => {
  // test方法编写测试用例
  test('add 1+2 = 3', function(){
    let res = math.add(1,2)
    expect(res).toBe(3)
  })
  test('add 2+2 = 5', function(){
    let res = math.add(2,2)
    expect(res).toBe(5)
  })
})

5.执行测试

yarn test

6.得到结果

会非常清晰的告诉你测试add方法这个describe下,期望值是5, 实际得到了4,所以失败。 如图

7.看一下测试覆盖率,执行test的时候加--coverage命令, 或者直接配置,每次test都生成测试覆盖率报告,coverage文件夹可以打开查看

"scripts": {
  "test": "jest --coverage",
},

8.执行yarn test命令行反馈math.js测试覆盖率100%,如图:

测试button组件

和测试一个普通函数不同,测试一个 React组件还需要两个关键的问题:

  • 怎么渲染待测试的组件
  • 怎么测试渲染出来的组件

1.安装和配置 Enzyme,

注意:enzyme还需要根据React的版本安装适配器,我的react是16,所以,下面安装enzyme-adapter-react-16

yarn add enzyme enzyme-adapter-react-16

2.创建测试文件

touch button.test.js
cd button.test.js

3.配置enzyme

import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
 
configure({ adapter: new Adapter() });

enzyme提供了三种渲染方式,render、mount、shallow,分别存在以下区别:

render采用的是第三方库Cheerio的渲染,渲染结果是普通的html结构,对于snapshot使用render比较合适。

  • shallow和mount对组件的渲染结果不是html的dom树,而是react树,如果你chrome装了react devtool插件,他的渲染结果就是react devtool tab下查看的组件结构,而render函数的结果是element tab下查看的结果。
  • shallow和mount的结果是个被封装的ReactWrapper,可以进行多种操作,譬如find()、parents()、children()等选择器进行元素查找;state()、props()进行数据查找,setState()、setprops()操作数据;simulate()模拟事件触发。
  • shallow只渲染当前组件,只能能对当前组件做断言;mount会渲染当前组件以及所有子组件,对所有子组件也可以做上述操作。一般交互测试都会关心到子组件,我使用的都是mount。但是mount耗时更长,内存啥的也都占用的更多,如果没必要操作和断言子组件,可以使用shallow。

4.用mount做例子

import * as React from 'react';
import { configure, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import CandyButton from '../button';

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

it('renders <CandyButton /> components', () => {
  const fn = jest.fn();
  const buttonWidth = 100;
  const buttonHeight = 100
  const wrapper = mount(
    <CandyButton
      title='candy_button'
      value={0}
      onAdd={fn}
    />);
  /** 获取到按钮 */
  const addButton = wrapper.find('.add_btn');
  /** 判断是否有这个元素*/
  expect(addButton.length).toBe(1);
  /** 判断内容*/
  expect(addButton.text()).toBe('按钮被点击了0次');
  /** 样式检查 */
  const buttonClass = `.add_btn`;
  expect(wrapper.find(buttonClass).prop('style')).toEqual({
    width: 100,
    height: 100
  });
  //  /** 模拟点击事件 */
  addButton.simulate('click');
  expect(wrapper.find(buttonClass).prop('style')).toEqual({
    width: buttonWidth + 10,
    height: buttonHeight + 10
  });
});

5.执行测试用例,查看测试报告

常用断言

// 判断2个值是否相等,注意:toBe不能判断对象,判断对象使用toEqual
expect(1+1).toBe(2);
// 递归检查对象的每个字端
expect({a: 1}).toEqual({a: 1});
// 判断不相等
expect(1+1).not.toBe(3);
// 判断是否为null
expect(0).toBeNull();
// 判断结果为true
expect(1).toBeTruthy();
// 判断结果为false
expect(0).toBeFalsy();
// 浮点数判断相等
expect(value).toBeCloseTo(0.3);
// 判断包含属性
expect('hello').toHaveProperty('he');
// 大于
expect(3).toBeGreaterThan(2);
// 大于等于
expect(3).toBeGreaterThanOrEqual(2);
// 小于
expect(2).toBeLessThan(3);
// 小于等于
expect(2).toBeLessThanOrEqual(3);
// promise
expect(promise.resolve('a')).resolves.toBe('a');
expect(promise.reject('b')).rejects.toBe('b');
// contain
expect(['a', 'b']).toCotain('b');
expect('123').not.toCotain('4');
expect({a: 1}).toCotainEqual({a: 1});
// match
expect('NBA').toMatch(/^NB/)
expect({name: 'candy', age: 18}).toMatchObject({name: 'candy'})

常用命令

{
  "nocache": "jest --no-cache", //清除缓存
  "watch": "jest --watchAll", //实时监听
  "coverage": "jest --coverage",  //生成覆盖测试文档
  "verbose": "npx jest --verbose" //显示测试描述
}