前言: 问了好些前端大神,回复都说,作好前端测试是个不简单的事情,特别是对于前端UI部分的测试。antd在UI上,能够说作得很好,可是看了他们的相关测试,也没有很完善。.javascript
目前来讲,前端测试在service和model层的应用,结合相关资料和本身遇到的坑,给你们分享一下。前端
项目中,用到的 babel等编译工具不是最新版本(6.0.0+),在当前最新版本24.0.0下,会报编译工具不合适的错误,把版本降至20.0.0,可使用。具体版本,能够根据项目中版本进行选择。java
项目中的预编译,主要是由babelrc 文件中配置,presets和plugins两项,这两项跟项目中,webpack中的配置保持一致node
presets: ['es2015', 'stage-3'],
plugins: ['transform-class-properties',
'syntax-dynamic-import',
'transform-runtime'
],
复制代码
若是项目中在node_modules中依赖了未编译的插件,须要进行下面的设置。不然,在.test用例中,没法引入依赖,从而会报错。webpack
transform: { // 将.js后缀的文件使用babel-jest处理
"^.+\\.js$": "babel-jest",
},
transformIgnorePatterns: // 未编译的依赖,须要在这边配置 ["<rootDir>/node_modules/(?!(snk-sse|javascript-state-machine))"],
复制代码
"jest": "jest --coverage"
复制代码
运行npm run jest,就会运行带有.test.js的全部文件web
在node里面,没有实际接口调用,若是测试涉及到接口调用的返回数据,就须要进行模拟数据。下面,介绍其中一种比较简单的方法:正则表达式
第一步,在 VideoService.js的同级目录,新建 mocks 文件夹,而后在文件夹里面,新建 VideoService.js(mock文件夹的文件名必须与的实际文件名一致)npm
// __mocks__/VideoService.js文件,
export default {
httpQuery() {
return new Promise((resolve)=>{
resolve(123123);
})
}
}
复制代码
第二步,在测试test.js文件中,须要加上jest.mock(),RtVideoService.httpChange调用了VideoService.js的接口,这里会查找__mocks__下面的文件,并使用VideoService模拟的数据数组
jest.mock('../services/VideoService.js'); // 必须存在,要否则会报错
test('RtVideoService', () => {
const obj = {
newer: 'linwenhuan@sinosafe.com.cn',
callback: { error: jest.fn() }
};
await RtVideoService.httpChange(null, obj); // 使用了__mocks__下的文件
expect(obj.callback.next.mock.calls.length).toBe(1);
})
复制代码
若是测试失败,第一件要检查的事就是,当仅运行这条测试时,它是否仍然失败。 在 Jest 中很容易地只运行一个测试 — — 只需暂时将 test 命令更改成 test.only:bash
test.only('this will be the only test that runs', () => {
expect(true).toBe(false);
});
test('this test will not run', () => { // 此条不会运行
expect('A').toBe('A');
});
复制代码
普通匹配器
test('two plus two is four', () => {
expect(2 + 2).toBe(4);
});
test('object assignment', () => {
const data = {one: 1};
data['two'] = 2;
expect(data).toEqual({one: 1, two: 2});
});
复制代码
布尔值匹配器
test('null', () => {
const n = null;
expect(n).toBeNull();
expect(n).toBeDefined();
expect(n).not.toBeUndefined();
expect(n).not.toBeTruthy();
expect(n).toBeFalsy();
});
test('zero', () => {
const z = 0;
expect(z).not.toBeNull();
expect(z).toBeDefined();
expect(z).not.toBeUndefined();
expect(z).not.toBeTruthy();
expect(z).toBeFalsy();
});
复制代码
字符串匹配器
数字匹配器
数组匹配器
自定义断言
expect.extend({
toBeDivisibleBy(received, argument) {
const pass = received % argument == 0;
if (pass) {
return {
message: () =>
`expected ${received} not to be divisible by ${argument}`,
pass: true,
};
} else {
return {
message: () => `expected ${received} to be divisible by ${argument}`,
pass: false,
};
}
},
});
test('even and odd numbers', () => {
expect(100).toBeDivisibleBy(2);
expect(101).not.toBeDivisibleBy(2);
});
复制代码
一、jest.fn()
// 举例
test('测试jest.fn()调用', () => {
let mockFn = jest.fn();
let result = mockFn(1, 2, 3);
// 断言mockFn的执行后返回undefined
expect(result).toBeUndefined();
// 断言mockFn被调用
expect(mockFn).toBeCalled();
// 断言mockFn被调用了一次
expect(mockFn).toBeCalledTimes(1);
// 断言mockFn传入的参数为1, 2, 3
expect(mockFn).toHaveBeenCalledWith(1, 2, 3);
})
// 实际运用
test('answer', () => {
const fn = { error: jest.fn() };
ExeService.answer(null, fn);
expect(fn.error.mock.calls.length).toBe(1); // 测试成功
});
复制代码
jest.fn()所建立的Mock函数还能够设置返回值,定义内部实现或返回Promise对象。
test('测试jest.fn()返回固定值', () => {
let mockFn = jest.fn().mockReturnValue('default');
// 断言mockFn执行后返回值为default
expect(mockFn()).toBe('default');
})
test('测试jest.fn()内部实现', () => {
let mockFn = jest.fn((num1, num2) => {
return num1 * num2;
})
// 断言mockFn执行后返回100
expect(mockFn(10, 10)).toBe(100);
})
test('测试jest.fn()返回Promise', async () => {
let mockFn = jest.fn().mockResolvedValue('default');
let result = await mockFn();
// 断言mockFn经过await关键字执行后返回值为default
expect(result).toBe('default');
// 断言mockFn调用后返回的是Promise对象
expect(Object.prototype.toString.call(mockFn())).toBe("[object Promise]");
})
复制代码
二、jest.mock(),改变函数的内部实现,上面已经有实际案例,不估重复讲述
三、jest.spyOn()
jest.spyOn()方法一样建立一个mock函数,可是该mock函数不只可以捕获函数的调用状况,还能够正常的执行被spy的函数。实际上,jest.spyOn()是jest.fn()的语法糖,它建立了一个和被spy的函数具备相同内部代码的mock函数
jest.mock('../services/VideoService.js'); // 必须存在,要否则会报错
test('RtVideoService', () => {
const obj = {
newer: 'linwenhuan@sinosafe.com.cn',
callback: { error: jest.fn() }
};
await RtVideoService.httpChange(null, obj); // 使用了__mocks__下的文件
const spyFn = jest.spyOn(VideoService, 'httpChange');
expect(spyFn).toHaveBeenCalled();
expect(obj.callback.next.mock.calls.length).toBe(1);
})
复制代码
在执行完写的测试用例后,你可能会想,有什么指标来讲明我写的测试用例,覆盖项目全不全?
"test": "jest --coverage"
复制代码
一、首先,在package的命令上,带有coverage,这时,执行成功所有用例,会输出如下结果:
%stmts是语句覆盖率(statement coverage):是否是每一个语句都执行了?
%Branch分支覆盖率(branch coverage):是否是每一个if代码块都执行了?
%Funcs函数覆盖率(function coverage):是否是每一个函数都调用了?
%Lines行覆盖率(line coverage):是否是每一行都执行了?
二、coverage 须要忽略的文件或文件夹
coveragePathIgnorePatterns: [
"\\\\node_modules\\\\",
"<rootDir>/src/utils/",
"<rootDir>/src/observers/",
"<rootDir>/lib/",
],
复制代码
三、执行完测试用例后,会在项目要目录,生成coverage文件夹,里面输出覆盖率报告