安装
-
create-react-app创建的应用自带Jest库,执行命令npm run test就会进入测试单元界面。同时提供强行运行所有单元测试代码、选择只用心满足过滤条件的单元测试用例等高级功能。 - 在项目中,我们配置了相关的参数,具体的配置如下
文件位置,以及目录写法
我们使用单元测试,一般的写法,是在待测试的方法或事组件的同级目录下,声明一个 __test__的文件夹,并在该文件夹下,命名一个以.test.js结尾的文件。
jest.config.js
module.exports = {
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}', '!src/**/*.d.ts'],
coverageDirectory: '<rootDir>/.tmp/coverage',
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: -10
}
},
moduleNameMapper: {
'^react-native$': 'react-native-web',
'^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy'
}, // 代表需要被Mock的资源名称
moduleFileExtensions: [
'web.js',
'js',
'web.ts',
'ts',
'web.tsx',
'tsx',
'json',
'web.jsx',
'jsx',
'node'
], // 代表支持加载的文件名
resolver: 'jest-pnp-resolver',
setupFiles: ['react-app-polyfill/jsdom'],
// 配置`setupTests.js`中`enzyme`的连接,使得`enzyme`配置生效
setupTestFrameworkScriptFile: '<rootDir>/src/setupTests.js',
testMatch: [
'<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}',
'<rootDir>/src/**/?(*.)(spec|test).{js,jsx,ts,tsx}'
], // 配置找到_test_下面的以Component.test.js,Component.spec.js
testEnvironment: 'jsdom', // 测试环境
testURL: 'http://localhost', // 它反映在诸如location.href之类的属性中
transform: {
'^.+\\.(js|jsx|ts|tsx)$': '<rootDir>/node_modules/babel-jest',
'^.+\\.css$': '<rootDir>/config/jest/cssTransform.js',
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)':
'<rootDir>/config/jest/fileTransform.js'
},
transformIgnorePatterns: [
'[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$',
'^.+\\.module\\.(css|sass|scss)$'
],
verbose: false
};
setUpTest.js
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
package.json的启动脚本配置
"scripts": {
"test": "node scripts/test.js",
},
script下面test.js
'use strict';
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'test';
process.env.NODE_ENV = 'test';
process.env.PUBLIC_URL = '';
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
throw err;
});
// Ensure environment variables are read.
require('../config/env');
const { execSync } = require('child_process');
const jest = require('jest');
const argv = process.argv.slice(2);
function isInGitRepository() {
try {
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
return true;
} catch (e) {
return false;
}
}
function isInMercurialRepository() {
try {
execSync('hg --cwd . root', { stdio: 'ignore' });
return true;
} catch (e) {
return false;
}
}
// Watch unless on CI, in coverage mode, or explicitly running all tests
if (!process.env.CI && argv.indexOf('--coverage') === -1 && argv.indexOf('--watchAll') === -1) {
// https://github.com/facebook/create-react-app/issues/5210
const hasSourceControl = isInGitRepository() || isInMercurialRepository();
argv.push(hasSourceControl ? '--watch' : '--watchAll');
}
jest.run(argv);
enzyme
三种渲染方式
-
shallow:浅渲染,是对官方的Shallow Renderer的封装。将组件渲染成虚拟DOM对象,只会渲染第一层,子组件将不会被渲染出来,使得效率非常高。不需要DOM环境, 并可以使用jQuery的方式访问组件的信息 -
render:静态渲染,它将React组件渲染成静态的HTML字符串,然后使用Cheerio这个库解析这段字符串,并返回一个Cheerio的实例对象,可以用来分析组件的html结构 -
mount:完全渲染,它将组件渲染加载成一个真实的DOM节点,用来测试DOM API的交互和组件的生命周期。用到了jsdom来模拟浏览器环境
常见方法
-
simulate(event, mock):模拟事件,用来触发事件,event为事件名称,mock为一个event object -
instance():返回组件的实例 -
find(selector):根据选择器查找节点,selector可以是CSS中的选择器,或者是组件的构造函数,组件的display name等 -
at(index):返回一个渲染过的对象 -
get(index):返回一个react node,要测试它,需要重新渲染 -
contains(nodeOrNodes):当前对象是否包含参数重点node,参数类型为react对象或对象数组 -
text():返回当前组件的文本内容 -
html(): 返回当前组件的HTML代码形式 -
props():返回根组件的所有属性 -
prop(key):返回根组件的指定属性 -
state():返回根组件的状态 -
setState(nextState):设置根组件的状态 -
setProps(nextProps):设置根组件的属性
编写测试用例
it
每个测试用例一个it函数代表,当然it还有一个别名,叫做test。
我们先看一个例子:
// ,第一个参数描述它的预期行为
it('当我们传入的参数都是数字的时候', () = > {
// 增加断言语句
})
- 第一个参数:用来表示我们预期这个测试用例的行为
- 第二个参数:是我们实际进行测试的逻辑代码书写的位置
describe
这个是一个测试的套件,在这个里面我们可以写多个it函数,这个的主要作用就是,我们有的时候,可能也需要对测试用例进行分类,这个时候,我们就用到了describe。我们看一下他的具体的写法
describe('这是一个小的逻辑单元', () => {
it('当我们传入的参数都是数字的时候', () => {
})
it('当我们传入的参数都是英文的时候', () => {
})
})
-
beforeAll在开始测试套件之前执行一次(在beforeEach之前) -
afterAll在结束测试套件中所有测试用例之后执行一次在(afterEach之后) -
beforeEach每个测试用例在执行之前都执行一次 -
afterEach每个测试用例在执行之后都执行一次
常见的断言
-
expect(value):要测试一个值进行断言的时候,要使用expect对值进行包裹 -
toBe(value):使用Object.is来进行比较,如果进行浮点数的比较,要使用toBeCloseTo -
not:用来取反 -
toEqual(value):用于对象的深比较 -
toMatch(regexpOrString):用来检查字符串是否匹配,可以是正则表达式或者字符串 -
toContain(item):用来判断item是否在一个数组中,也可以用于字符串的判断 -
toBeNull(value):只匹配null -
toBeUndefined(value):只匹配undefined -
toBeDefined(value):与toBeUndefined相反 -
toBeTruthy(value):匹配任何使if语句为真的值 -
toBeFalsy(value):匹配任何使if语句为假的值 -
toBeGreaterThan(number): 大于 -
toBeGreaterThanOrEqual(number):大于等于 -
toBeLessThan(number):小于 -
toBeLessThanOrEqual(number):小于等于 -
toBeInstanceOf(class):判断是不是class的实例 -
anything(value):匹配除了null和undefined以外的所有值 -
resolves:用来取出promise为fulfilled时包裹的值,支持链式调用 -
rejects:用来取出promise为rejected时包裹的值,支持链式调用 -
toHaveBeenCalled():用来判断mock function是否被调用过 -
toHaveBeenCalledTimes(number):用来判断mock function被调用的次数 -
assertions(number):验证在一个测试用例中有number个断言被调用 -
extend(matchers):自定义一些断言
实战
对于方法的测试用例的编写
//bytesCount: 获取字符串长度,英文占一个字符,中文占两个字符
import { bytesCount } from '../bytesCount';
describe('测试计算字符串长度', () => {
it('当传入的字符串为汉字', () => {
expect(bytesCount('哈哈哈哈')).toBe(8);
});
it('当传入的字符串为数字', () => {
expect(bytesCount(123)).toBe(3);
});
it('当传入的字符串为英文', () => {
expect(bytesCount('abd')).toBe(3);
});
it('当传入的字符串为特殊字符', () => {
expect(bytesCount('~|@%&*')).toBe(6);
});
it('当传入的字符串为null', () => {
expect(bytesCount(null)).toBe(0);
});
it('当传入的字符串为undefined', () => {
expect(bytesCount(undefined)).toBe(0);
});
it('当传入的字符串为数组', () => {
expect(bytesCount([])).toBe(0);
});
});
对于组件的测试用例的书写
//ArInput: 组件的作用,是用来在输入框中输入,当检测到,或是换行的时候,会在输入框下生成一个标签
import React from 'react';
import { mount, shallow } from 'enzyme';
import ArInput from '../index';
let wrapper;
const props = {
max: 5,
callback: jest.fn(),
tagsChange: jest.fn(),
placeholder: '非英文输入',
tagsInit: [123, 333]
};
describe('Test MonthPicker Component', () => {
beforeEach(() => {
wrapper = mount(<ArInput {...props} />);
});
it('初始化生成的标签是否正常', () => {
expect(wrapper.find('.ant-tag').length).toEqual(2);
});
it('placeHolder显示是否正常', () => {
expect(wrapper.find('input').getDOMNode().placeholder).toEqual('非英文输入');
});
it('输入事件是否可以正确的使用', () => {
const mockEvent = {
target: {
value: '13,'
}
};
wrapper.find('input').simulate('keyup', mockEvent);
expect(props.callback).toHaveBeenCalledWith('13,');
expect(wrapper.state('tags').length).toEqual(3);
expect(wrapper.state('tags')[2]).toEqual('13');
});
});
踩过的坑
- 在项目中引入了
antdesign,所有我们在进行配置的时候,渲染的方式只能选用mount,这样的话,渲染的会有一点慢 - 我们使用了
css-module的形式来编写样式,这会导致我们在通过类名查找dom的时候,找不到节点 - 我们使用
react框架,在通过节点进行取值的时候,由于是虚拟dom,会拿不到节点 - 我们为了简化路径,在
webpack中配置了公共的路径,但是在在单元测试中,引入在webpack中配置的路径,会有问题。










网友评论