1. 安装
使用 yarn 安装 Jest:
yarn add --dev jest
或使用 npm 安装:
npm install --save-dev jest
2. 配置
(目前我是针对Javascript,Typescript做单元测试,暂时不考虑对页面DOM的操作,后期再补这一块)
Jest 将根据你的项目提出一系列问题,并且将创建一个基础配置文件
jest --init
生成jest.config.js
如果使用babel,需要额外依赖
yarn add --dev babel-jest @babel/core @babel/preset-env
然后在根目录创建babel.config.js,来配置Babel使其能够兼容当前的Node版本。
babel.config.js
module.exports = {
presets: [['@babel/preset-env', {targets: {node: 'current'}}]],
};
注意:
Jest版本24 就不再支持Babel 6,推荐使用Babel7
非要Babel 6就使jest固定在23版本
使用Typescript
yarn add --dev @babel/preset-typescript
安装依赖之后,将@babel/preset-typescript添加到babel.config.js中的presets列表中
不过,将 TypeScript 和 Babel 一起使用时有一些 注意事项
由于 Babel 对 Typescript 的支持是通过代码转换(Transpilation)实现的, 而 Jest 在运行时并不会对你的测试用例做类型检查。 如果你需要此功能,可以使用ts-jest,或者单独(或作为构建流程的一部分)直接运行 TypeScript 编译器 tsc 。
额外依赖:
yarn add --dev @types/jest
--watchAll
监控所有文件变化
"scripts": {
"test": "jest --watchAll"
}
--watch
每次只重新测试修改过后的文件
"scripts": {
"test": "jest --watch"
}
3. Matchers
常用的Matchers如下:
toBe
toBeNull
toBeUndefined
toBeDefined
toBeWithinRange
toBeTruthy
toBeFalsy
toBeGreaterThan
toBeGreaterThanOrEqual
toBeLessThan
toBeLessThanOrEqual
toMatch
toContain
toThrow
toBeCloseTo
toHaveLength
toBeInstanceOf
toHaveBeenCalled
toHaveBeenCalledTimes // 被调用的次数
toHaveReturned // function returned
toHaveReturnedTimes // times
toHaveReturnedWith // 函数返回值
arrayContaining
objectContaining
stringContaining
typescript写法:
declare global {
namespace jest {
interface Matchers<R> {
toBeWithinRange(a: number, b: number): R;
}
}
}
1. 举第一个例子
toBe、toBeNull、toBeUndefined、toBeDefined、toBeTruthy、toBeFalsy、toBeGreaterThan、toBeGreaterThan、toBeLessThan、toBeLessThanOrEqual
我有一个函数correctData用来计算时间总长的,如果计算正常,应该是返回总和为24*60*60*1000的值,对这个函数进行测试:
test.spec.js文件:
import { correctData } from "../src/testFunction";
test('calculate 24', () => {
let result = correctData(data)
let duration = 0
for (const key in result) {
duration += result[key]
}
let final = duration/(1000*60*60) // 计算正常应该是返回final === 24
expect(final).toBe(24)
expect(final).not.toBeNull()
expect(final).not.toBeUndefined()
expect(final).toBeDefined()
expect(final === 24).toBeTruthy()
expect(final !== 24).toBeFalsy()
expect(final).toBeGreaterThan(20)
expect(final).toBeGreaterThanOrEqual(24)
expect(final).toBeLessThan(48)
expect(final).toBeLessThanOrEqual(24)
})
这些匹配器看例子就懂什么意思了
在控制台跑一下
简单的例子passed!
2. 举第二个例子
toBeCloseTo,toMatch, toContain, toHaveLength, toBeInstanceOf
test('other Matchers', () => {
let m = 0.1+0.2
let arr = [1,2,3,4,5]
expect(m).toBeCloseTo(0.3) // 首先0.1+0.2在js里肯定不等于0.3,只能无限接近
expect(m.toString()).toMatch('0.3')
expect(arr).toContain(4)
expect(arr).toHaveLength(5)
expect(arr).toBeInstanceOf(Array) // 通过原型判断
})
3. 举第三个例子
toHaveBeenCalled
这个就比较有用了,这个用来确保函数被调用
test.js
export function callFn(callback){
if(callback) {
callback()
}
}
import { callFn } from "../src/test";
test('Matchers——toHaveBeenCalled', () => {
let fn = jest.fn()
callFn(fn)
expect(fn).toHaveBeenCalled();
})
4. 举第四个例子
toThrow测试函数被调用时的抛出情况
testFunction.js
export function fnThrow(){
try {
let result = axios.get('http://xxx.xxx.xxx.xxx/api/model/v1/s')
// 这里是错误的url
} catch (e) {
throw new Error('someting wrong');
}
}
testFunction.spec.js
import { fnThrow } from "../src/testFunction";
test('throw Matchers', () => {
expect(() => fnThrow()).toThrow()
expect(() => fnThrow()).toThrow(Error)
expect(() => fnThrow()).toThrow('someting wrong')
})
看一下测试结果
简单的匹配器懂了,就看看怎么使用了
4. 异步代码
第一种:
使用done()方法
使用done(),测试用例会一直执行到done结束
第二种:
如果返回是Promise对象,可以直接使用return
如果promise 请求成功肯定不会走catch,但是测试会依然通过
// 因此需要加上下面一句,指定必须只能执行一次expect
expect.assertions(1)
第三种:
如果返回的是一个Promise对象,可以直接使用return + resolves/rejects写法
.resolves和.rejects 可以将promise的值返回,方便直接链式调用匹配器
第四种:
如果返回的是一个 Promise 对象,async + await
Jest的钩子函数
- beforeAll:所有测试之前执行
- afterAll:所有测试执行完之后
- beforeEach:每个测试实例之前执行
- afterEach:每个测试实例完成之后执行
5. Mock函数
jest.fn(),jest.mock()和jest.spyOn()
jest.fn()
这是创建Mock最简单的方式,如果没有定义函数内部的实现,jest.fn()会返回undefined作为返回值
jest.fn()所创建的Mock函数还可以设置返回值,定义内部实现或返回Promise对象。
jest.mock()
封装的请求方法可能在我们在其他模块被调用的时候,并不需要进行实际的请求 可以使用jest.mock()去mock整个模块
jest.spyOn()
jest.spyOn()方法同样创建一个mock函数,但是该mock函数不仅能够捕获函数的调用情况,还可以正常的执行被spy的函数。实际上,jest.spyOn()是jest.fn()的语法糖,它创建了一个和被spy的函数具有相同内部代码的mock函数。
方法:
// 使用模拟函数实例化
mockFn.mock.instances
// 清除所有信息
mockFn.mockClear()
// 执行所有mockClear操作,并删除任何模拟返回值
mockFn.mockReset()
// 恢复原始实现
mockFn.mockRestore()
// 接受一个函数
mockFn.mockImplementation(fn)
// 接受一个函数执行一次
mockFn.mockImplementationOnce(fn)
// 函数名
mockFn.mockName(value)
//
mockFn.mockReturnThis()
// 返回值
mockFn.mockReturnValue(value)
// 返回值,执行一次
mockFn.mockReturnValueOnce(value)
//
mockFn.mockResolvedValue(value)
//
mockFn.mockResolvedValueOnce(value)
//
mockFn.mockRejectedValue(value)
//
mockFn.mockRejectedValueOnce(value)
mockFn.mockReturnThis() 语法糖:
jest.fn(function () {
return this;
});
6. 例子
tesxAxios.js
import axios from "axios";
export function httpTest() {
return axios.get('http://192.168.1.125/api/model/v1/things')
}
export function eventTest() {
return axios.get('http://192.168.1.125/api/event/v1/events?filter=%7B%22thingId%22%3A%221c84e0271f9a4edea6cf9df9330c94b8%22%2C%22typeId%22%3A%22main.FX3U80M%22%7D&select=severity%2C10301%2C10323%2C10314%2C10315%2C10316%2C10373%2C10374%2C10309%2C10318%2C10319%2C10320%2C10321%2C10322%2C10339%2C10341%2C10343%2C10334%2C10335%2C10325%2C10326%2C10327%2C10328%2C10369%2C10370%2C10371%2C10372%2C10336%2C10384')
}
export function wrongHttpTest() {
return axios.get('http://192.168.1.125/api/model/v1/thingss')
}
export function hasPost(param) {
return axios.post('http://192.168.1.125/api/event/v1/events', {...param})
}
export function httpPromiseTest() {
return new Promise((resolve) => {
resolve('succ')
})
}
testAxios.spec.js
import {httpTest, wrongHttpTest, hasPost, eventTest, httpPromiseTest } from "../src/testAxios";
// http get获取数据(获取数据不为空)
test('http get方法', (done) => {
httpTest().then(res => {
expect(res.data).not.toBe(null)
})
done() // 如果不加 done,还没执行到 .then 方法,测试用例已经结束了
})
// http get 404
test('http get方法404', (done) => {
//写法一
// wrongHttpTest().catch((e)=>{
// expect(e.response.status.toString()).toMatch('404')
// done()
// })
//写法二
expect(wrongHttpTest()).rejects.toMatch('404')
})
test('http post', async(done) => {
const service = {
"typeId": "main.Q60EHCPU",
"_time": "2021-12-13T00:21:56.797Z",
"thingId": "807bd74656d94a70aaf5cfda16f27333",
"M3000": true,
"severity": 1
};
try {
const before = await eventTest()
let count = before.data.events.length
await hasPost(service)
setTimeout(async function (){
const after = await eventTest()
expect(after.data.events.length).toBe(count+1)
}, 2500)
// done()
} catch (err) {
// done(err)
}
});
test('http promise', () =>{
return httpPromiseTest().then(res => {
expect(res).toMatch('succ')
})
})
test('http promise catch', () =>{
expect.assertions(1) // 加上就会报错了
return httpPromiseTest().catch(e => {
expect(e).toMatch('Error')
})
})
testMork.js
import axios from "axios";
export default {
async getdata(){
return axios.get('http://192.168.1.125/api/model/v1/things').then(data =>{
console.log('fetch Data')
return data
})
}
}
testMork.spec.js
import axios from "axios";
import testMork from '../src/testMork'
jest.mock('axios')
// jest.mork('../src/control.js')
test('测试jest.fn()调用', () => {
let mockFn = jest.fn();
// let mockFn = jest.fn().mockName('getdata')
let result = mockFn('hello');
// 断言mockFn的执行后返回undefined
expect(result).toBeUndefined();
// 断言mockFn被调用
expect(mockFn).toBeCalled();
// 断言mockFn被调用了一次
expect(mockFn).toBeCalledTimes(3);
// 断言mockFn传入的参数为1, 2, 3
expect(mockFn).toHaveBeenCalledWith('hello');
})
test('测试jest.fn()返回值', () => {
let mockFn = jest.fn().mockReturnValue('default');
// 断言mockFn执行后返回值为default
expect(mockFn()).toBe('default');
})
test('mock', async () => {
axios.get.mockResolvedValue({
data: '456'
})
expect(await testMork.getdata()).toStrictEqual({
data: '456'
});
})
test('jest.spyOn', async() => {
expect.assertions(1);
const spyFn = jest.spyOn(testMork);
await testMork.getdata();
expect(spyFn).toHaveBeenCalled();
})
testFunction.js
export function correctData (data) {
let lastRunState = data.service.lastTrueTime === '0001-01-01T00:00:00Z' ?
0 : Date.parse(data.service.lastTrueTime);
let lastWaitState = data.stop.lastTrueTime === '0001-01-01T00:00:00Z' ?
0 : Date.parse(data.stop.lastTrueTime);
let nowLastState = Math.max(lastRunState, lastWaitState)
let offlineTime = Date.parse(data.end) - nowLastState
let alramTime = data.alarm.lastValue ? data.alarm.trueDuration - offlineTime : data.alarm.trueDuration
let waitTime
let runTime
let timeRange = Date.parse(data.end) - Date.parse(data.start)
if (lastRunState > lastWaitState) {
waitTime = data.stop.trueDuration
runTime = timeRange - offlineTime - data.stop.trueDuration
} else {
runTime = data.service.trueDuration
waitTime = timeRange - offlineTime - data.service.trueDuration
}
let countRun = (runTime / (waitTime+runTime))*alramTime
let countWait = (waitTime / (waitTime+runTime))*alramTime
waitTime = waitTime - countWait
runTime = runTime - countRun
return {
service: runTime,
stop: waitTime,
offline: offlineTime,
alarm: alramTime
}
}
export function fnThrow(){
try {
let result = axios.get('http://192.168.1.125/api/model/v1/thingss')
} catch (e) {
throw new Error('someting wrong');
}
}
export function callFn(callback){
if(callback) {
callback()
}
}
testFunction.spec.js
import { correctData, fnThrow, callFn } from "../src/testFunction";
let data = {
"alarm":{
"falseCnt":4,
"falseDuration":12600000,
"firstFalseTime":"2021-12-09T02:00:00Z",
"firstTrueTime":"2021-12-09T00:00:00Z",
"lastFalseTime":"2021-12-09T06:00:00Z",
"lastTrueTime":"2021-12-09T06:36:00Z",
"lastValue":true,
"trueCnt":6,
"trueDuration":73800000
},
"end":"2021-12-09T16:00:00Z",
"offline":{
"falseCnt":0,
"falseDuration":-83010264511686,
"firstFalseTime":"0001-01-01T00:00:00Z",
"firstTrueTime":"0001-01-01T00:00:00Z",
"lastFalseTime":"0001-01-01T00:00:00Z",
"lastTrueTime":"0001-01-01T00:00:00Z",
"lastValue":false,
"trueCnt":0,
"trueDuration":83010348331686
},
"service":{
"falseCnt":5,
"falseDuration":14700000,
"firstFalseTime":"2021-12-09T01:00:00Z",
"firstTrueTime":"2021-12-09T00:00:00Z",
"lastFalseTime":"2021-12-09T06:30:00Z",
"lastTrueTime":"2021-12-09T06:36:00Z",
"lastValue":true,
"trueCnt":5,
"trueDuration":71700000
},
"start":"2021-12-08T16:00:00Z",
"stop":{
"falseCnt":5,
"falseDuration":71700000,
"firstFalseTime":"2021-12-09T00:00:00Z",
"firstTrueTime":"2021-12-09T01:00:00Z",
"lastFalseTime":"2021-12-09T06:36:00Z",
"lastTrueTime":"2021-12-09T06:30:00Z",
"lastValue":false,
"trueCnt":5,
"trueDuration":14700000
}
}
test('calculate 24', () => {
let result = correctData(data)
let duration = 0
for (const key in result) {
duration += result[key]
}
let final = duration/(1000*60*60)
expect(final).toBe(24)
expect(final).not.toBeNull()
expect(final).not.toBeUndefined()
expect(final).toBeDefined()
expect(final === 24).toBeTruthy()
expect(final !== 24).toBeFalsy()
expect(final).toBeGreaterThan(20)
expect(final).toBeGreaterThanOrEqual(24)
expect(final).toBeLessThan(48)
expect(final).toBeLessThanOrEqual(24)
expect(final).toBeWithinRange(1,25)
})
test('throw Matchers', () => {
expect(() => fnThrow()).toThrow()
expect(() => fnThrow()).toThrow(Error)
expect(() => fnThrow()).toThrow('someting wrong')
})
test('other Matchers', () => {
let m = 0.1+0.2
let arr = [1,2,3,4,5]
expect(m).toBeCloseTo(0.3)
expect(m.toString()).toMatch('0.3')
expect(arr).toContain(4)
expect(arr).toHaveLength(5)
expect(arr).toBeInstanceOf(Array)
})
test('Matchers——toHaveBeenCalled', () => {
let fn = jest.fn(() =>{})
callFn(fn)
expect(fn).toHaveBeenCalled();
})