关于前端单元测试的好处自没必要说,基础的介绍和知识能够参考以前的博客连接:React Native单元测试。在软件的测试领域,测试主要分为:单元测试、集成测试和功能测试。前端
前端的测试框架有不少:mocha, jasmine, ava, testcafe, jest,他们都有各自擅长的领域和特色,而咱们采用的jest框架具备以下的一些特色:react
# yarn
yarn add --dev jest
# npm
npm install --save-dev jest
复制代码
咱们编写一个被测试文件的sum.js,代码以下:git
function sum(a, b) {
return a + b;
}
module.exports = sum;
复制代码
而后,咱们添加一个名为sum.test.js的测试文件,注意命名时遵循xxx.test.js的命名规则。es6
const sum = require(‘./sum'); test('adds 1 + 2 to equal 3', () => { expect(sum(1, 2)).toBe(3); }); 复制代码
“断言”一般是给程序开发人员本身使用,而且在开发测试期间使用,用于判断在某些逻辑条件下会执行某种预期的结果。Jest框架内置了丰富的断言语句,详细的能够参考Jest 的Expect。此处,列举一些经常使用的:github
.toBe(value)
.toHaveBeenCalled()
.toBeFalsy()
.toEqual(value)
.toBeGreaterThan(number)
.toBeGreaterThanOrEqual(number)
复制代码
举个例子,下面是一个被测试的文件Hook.js。npm
export default class Hook {
constructor() {
this.init();
}
init() {
this.a = 1;
this.b = 1;
}
sum() {
return this.a + this.b;
}
}
复制代码
Hook.js主要实现两个数字相加的功能,而后咱们编写一个测试文件Hook.test.js。编程
import Hook from '../src/hook';
describe('hook', () => {
const hook = new Hook;
// 每一个测试用例执行前都会还原数据,因此下面两个测试能够经过。
beforeEach( () => {
hook.init();
})
test('test hook 1', () => {
hook.a = 2;
hook.b = 2;
expect(hook.sum()).toBe(4);
})
test('test hook 2', () => {
expect(hook.sum()).toBe(2);// 测试经过
})
})
复制代码
而后,在控制台执行yarn jest命令,便可运行单元测试,执行完成后会给出相应的结果。例如: api
jest 测试提供了一些测试的生命周期 API,能够辅助咱们在每一个 case 的开始和结束作一些处理。 这样,在进行一些和数据相关的测试时,能够在测试前准备一些数据,在测试完成后清理测试数据。这部分的知识能够参考官方的全局API。promise
这里列举4个主要的生命周期勾子:浏览器
BeforeAll(() => {
console.log('before all tests to excute !')
})
BeforeEach(() => {
console.log('before each test !')
})
AfterAll(() => {
console.log('after all tests to excute !')
})
AfterEach(() => {
console.log('after each test !')
})
Test('test lifecycle 01', () => {
expect(1 + 2).toBe(3)
})
Test('test lifecycle 03', () => {
expect(2 + 2).toBe(4)
})
复制代码
mock测试就是在测试过程当中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来建立以便继续进行测试的测试方法。Mock函数一般会提供如下三种特性:
jest.fn()是建立Mock函数最简单的方式,若是没有定义函数内部的实现,jest.fn()会返回undefined做为返回值。例如:有两个被测试代码every.js和foreach.js。代码以下: every.js
function every(array, predicate) {
let index = -1
const length = array == null ? 0 : array.length
while (++index < length) {
if (!predicate(array[index], index, array)) {
return false
}
}
return true
}
export default every
复制代码
foreach.js
function foreach(arr, fn) {
for(let i = 0, len = arr.length; i < len; i++) {
fn(arr[i]);
}
}
module.exports = foreach;
复制代码
下面是测试用例mock.test.js文件的代码:
import foreach from '../foreach';
import every from '../every';
describe('mock test', () => {
it('test foreach use mock', () => {
// 经过jest.fn()生成一个mock函数
const fn = jest.fn();
foreach([1, 2, 3], fn);
//测试mock函数被调用了3次
expect(fn.mock.calls.length).toBe(3);
// 测试第二次调用的函数第一个参数是3
expect(fn.mock.calls[2][0]).toBe(3);
})
it('test every use mock return value', () => {
const fn = jest.fn();
fn
.mockReturnValueOnce(true)
.mockReturnValueOnce(false);
const res = every([1, 2, 3, 4], fn);
expect(fn.mock.calls.length).toBe(2);
expect(fn.mock.calls[1][1]).toBe(1);
})
it('test every use mock mockImplementationOnce', () => {
const fn = jest.fn((val, index) => {
if (index == 2) {
return false;
}
return true;
});
const res = every([1, 2, 3, 4], fn);
expect(fn.mock.calls.length).toBe(3);
expect(fn.mock.calls[1][1]).toBe(1);
})
})
复制代码
测试代码时能够忽略模块的依存关系,进行手动mock。例如,有一个测试文件sum2.js。
function sum2(a, b) {
if (a > 10) return a * b;
return a + b;
}
export default sum2;
复制代码
若是要mock 一个sum2.js 文件的话,须要在sum2.js 同级目录下新建文件夹__mock__,而后在此文件下新建文件同名 sum2.js,而后mock返回100。
export default function sum2(a, b) {
return 100;
}
复制代码
而后,新建一个mock_file.test.js测试文件。
jest.mock('../sum2');
import sum2 from '../__mock__/sum2';
it('test mock sum2', () => {
//由于此时访问的是__mock__文件夹下的sum2.js因此测试经过
expect(sum2(1, 11111)).toBe(100);
})
复制代码
在实际开发过程当中,常常会遇到一些异步的JavaScript代码。当有异步方式运行的代码的时候,Jest须要知道当前它测试的代码是否已经完成,而后它才能够转移动另外一个测试中,也就是说,测试的用例必定要在测试对象结束以后才可以运行。Jest的异步测试主要分为3种:
done的例子以下:
function fetchData(call) {
setTimeout(() => {
call('peanut butter1')
},1000);
}
test('the data is peanut butter', (done) => {
function callback(data) {
expect(data).toBe('peanut butter');
done()
}
fetchData(callback);
});
复制代码
由于superagent库支持 promise和async/await方式,因此用superagent举例,实际项目开发可能会涉及到promise(es6之前的写法)和async/await(最新的写法),你们能够根据实际状况编写测试代码。
import superagent from 'superagent';
const target = 'http://www.baidu.com';
describe('test promise async', () => {
it('test done', done => {
superagent.get(target).end((err, res) => {
expect(res).toBeTruthy();
done();
});
});
it('test promise', () => {
return superagent.get(target).then((res) => {
expect(res).toBeTruthy();
});
});
it('test async/await', async () => {
const res = await superagent.get(target);
expect(res).toBeTruthy();
});
});
复制代码
注意,使用superagent框架进行异步测试时,请确保你的项目安装了superagent依赖。
快照测试第一次运行的时候会将被测试ui组件在不一样状况下的渲染结果保存一份快照文件,后面每次再运行快照测试时,都会和第一次的比较,除非执行“yarn test – -u”命令删除快照文件。例如,有一个文件reactComp.js.
import React from 'react';
export default class reactComp extends React.Component {
render() {
return (
<div>我是react组件 </div>
)
}
}
复制代码
而后,编写一个测试用例文件reactComp.test.js。
import React from 'react';
import renderer from 'react-test-renderer';
import RC from '../reactComp';
test('react-comp snapshot test', () => {
const component = renderer.create(<RC />);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
})
test('react-comp snapshot test2', () => {
const component = renderer.create(<RC />);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
})
复制代码
执行测试命令,会在test目录下生成一个__snapshots__目录,在此目录下会与一个快照文件,格式以下:
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`react-comp snapshot test 1`] = `
<div>
我是react组件
</div>
`;
exports[`react-comp snapshot test2 1`] = `
<div>
我是react组件
</div>
`;
复制代码
若是被测试代码有正常更新,可使用“jest --updateSnapshot ”命令从新更新缓存文件。