Jest 简介

Jest是Facebook开源的一套JavaScript测试框架, 它集成了断言、JSDom、覆盖率报告等开发者所需要的所有测试工具。

Jest基本环境搭建

项目搭建

这里我们使用vite 提供的一套社区模板直接搭建我们的项目,基于react + vite

# 使用git克隆模板
 git clone https://github.com/SafdarJamal/vite-template-react.git
# 进入我们的项目
 cd vite-template-react
# 安装依赖
 yarn 
# 启动项目
 yarn dev

project

安装Jest 和 初始化Jest配置文件

# 安装
 yarn add jest
# 执行该命令会有一系列选项说明给你填,基本我们点确认就行了,
# 执行完后会在项目根项目生成jest.config.js 配置文件
 npx jest --init

addJest

简单案例测试

我们在src的目录下新建一个utils的目录,在下面新建一个calc.js的文件夹,在里面我们新建两个简单的加减的函数

function addSum(num1, num2) {
	return num1 + num2;
}
function reduceNum(num1, num2) {
	return num1 - num2;
}
module.exports = {
    addSum,
    reduceNum
}

然后我们在src的根目录下新建一个App.test.js文件,这是jest文件的格式,我们使用jest的测试方法来模拟测试调用calc.js里面的两个函数返回的结果是否通过

// App.test.js 

const { addSum, reduceNum } = require('./utils/calc')

test("测试:10+10 = 20", () => {
    expect(addSum(10,10)).toBe(20)
})

test('测试:10-8= 2', () => {
    expect(reduceNum(10,8)).toBe(2)
});

然后我们运行我们的jest测试,你可以使用npx jest命令也可以在package.json文件在script新增一条命令,方便我们运行测试

测试结果

这里故意将写成10-8的结果写成1,所以我们的符合预期的结果就是有addSum计算的结果通过,而reduceNum计算的结果不通过,当我们运行yarn test时,jest就会自动帮我们运行测试用例并返回结果

测试覆盖率生成

我们直接运行yarn coverage命令(package.json里有配置),在我们的目录下会生成一个coverage的文件夹,而且这个文件夹名称是可以自定义配置的,如果是多个人同事开发的话可以自定义自己的名称,我们只需要在jest.config.js中配置 然后在我们目录下会生成coverage的一个目录,我们点进去找到lcov-report下的index.html文件,用浏览器打开就会看到测试报告

jest 中常用的匹配器

{% note info green %} toBe(): 完全相等,精确匹配 toEqual(): 值相等匹配 {% endnote %}

toBe()

test('toBe()匹配器', () => {
      // 完全相等匹配
    const a = { test: 1 };
    const b = a
    // expect(a).toBe({test:1}) // 不通过
    expect(a).toBe(b) // 通过
});

toEqual()

test('toEqual()匹配器', () => {
    // 值相等匹配
    const a = {name:'test'};
    // expect(a).toBe({name:'test'}) //  不通过
    expect(a).toEqual({name:'test'}) // 通过
});

{% note info green %} toBeNull(): 仅匹配null toBeUndefined(): 仅匹配未定义 toBeDefined(): 仅匹配定义的 toBeTruthy(): 匹配为true的内容 toBeFalsy(): 匹配为false的内容 {% endnote %}

toBeNull()

test('toBeNull()匹配器', () => {
    // 仅匹配null值
    const a = null
    expect(a).toBeNull() // 通过
});

toBeUndefined()

test('toBeUndefined()匹配器', () => {
    // 仅匹配undefined值
    const a = undefined
    expect(a).toBeUndefined () // 通过
});

toBeDefined()

test('toBeDefined()匹配器', () => {
    // 有值就通过
    const a = ""
    expect(a).toBeDefined () // 通过
});

toBeTruthy()

test('toBeTruthy()匹配器', () => {
    // 仅匹配真值
    const a = true
    expect(a).toBeTruthy() // 通过
});

toBeFalsy()

test('toBeFalsy()匹配器', () => {
    // 仅匹配假值
    const a = false
    expect(a).toBeFalsy() // 通过
});

{% note info green %} toBeGreaterThan(): 大于 toBeGreaterThanOrEqual(): 大于等于 toBeLessThan(): 小于 toBeLessThanOrEqual(): 小于等于 toBeCloseTo(): 专门用于匹配两个浮点数是否相等,如果用toEqual()会造成误差 {% endnote %}

toBeGreaterThan()

test('toBeGreaterThan()匹配器', () => {
    // 大于 
    const a = 0
    expect(a).toBeGreaterThan(-1) // 通过
});

toBeGreaterThanOrEqual()

test('toBeGreaterThanOrEqual()匹配器', () => {
    // 大于等于 
    const a = 0
    expect(a).toBeGreaterThanOrEqual(0) // 通过
});

toBeLessThan()

test('toBeLessThan()匹配器', () => {
    // 小于 
    const a = 0
    expect(a).toBeLessThan(1) // 通过
});

toBeLessThanOrEqual()

test('toBeLessThanOrEqual()匹配器', () => {
    // 小于等于
    const a = 0
    expect(a).toBeLessThanOrEqual(0) // 通过
});

toBeCloseTo()

test('toBeCloseTo()匹配器', () => {
    // 解决浮点数出现精度问题
    const a = 0.1 + 0.2;
    // expect(a).toEqual(0.3) // 不通过
    expect(a).toBeCloseTo(0.3) // 通过
});

{% note info green %} toMatch(): 根据字符串或者正则表达是否匹配 {% endnote %}

toMatch()

test('toMatch()匹配器', () => {
    // 根据字符串或者正则表达是否匹配
    const a = 'Christoph';
    expect(a).toMatch('stop');
    expect(a).toMatch(/stop/);
});

{% note info green %} toContain(): 根据字符串或者正则表达是否匹配 {% endnote %}

toContain()

test('toContain()匹配器', () => {
    // 匹配数组是否包含某一项值
    const a = ['test1', 'test2', 'test3'];
    // 也可以匹配Set
    const newArr = new Set(a);
    expect(a).toContain('test'); // 不通过
    expect(a).toContain('test1'); // 通过
    expect(newArr).toContain('test1'); // 通过
});

{% note info green %} toThrow(): 测试某个特定函数在调用时是否抛出错误 {% endnote %}

toThrow()

test('toThrow()匹配器', () => {
    // 匹配异常
	expect(() => compileAndroidCode()).toThrow();
	expect(() => compileAndroidCode()).toThrow(Error);

	// You can also use the exact error message or a regexp
	expect(() => compileAndroidCode()).toThrow('you are using the wrong JDK');
	expect(() => compileAndroidCode()).toThrow(/JDK/);
});

让Jest支持ES6写法

Jest默认不支持ES6的写法,当我们导入一个文件时必须是用require()到导入,导出使用module.export = {...}, 要想Jest支持import的写法必须要安装babel转换相关的插件:@babel/core@babel/preset-env ,安装好这两个插件后,然后在根目录下新建一个.babelrc 文件,然后在里面写上下面的配置,完成后我们就可以使用import 相关的写法了

// .babelrc 配置
{
    "presets": [
        [
            "@babel/preset-env",{
                "targets":{
                    "node":"current"
                }
            }
        ]
    ]
}

异步代码测试的几种方式

接口模拟

直接新建一个data.json文件,然后通过axios请求

{
    "success":true
}

请求接口

// 第一种方式
export function fetchOneData(fn) {
    axios.get('http://localhost:3000/data.json').then(res => {
        // console.log(res.data,'res......')
        fn(res.data)
    })
}
// 第二种方式
export function fetchTwoData() {
    return axios.get('http://localhost:3000/data.json')
}
// 第三种方式
export function fetchThreeData() {
    return axios.get('http://localhost:3000/data.json')
}

异步测试(一)

{% note info green %} 使用done参数,Jest将会等待知道调用done回调,然后再完成测试 {% endnote %}

// fetch.test.js
//第一种,使用done完成异步函数的测试
test('加done', (done) => {
    // 错误!不加done的话,就算接口不存在,这个函数一样是通过的
    fetchOneData(res => {
        expect(res).toEqual({ success: true })
    })
    // 正确
    fetchOneData(res => {
        expect(res).toEqual({ success: true })
        done()  // 必须要加done才能正确通过异步函数测试
    })
});

异步测试(二)

{% note info green %} 使用return,如果接口返回的是一个Promise对象的话,return会等待Promise返回然后再完成测试 {% endnote %}

// fetch.test.js
//第二种,使用return完成异步函数的测试
test('加return', () => {
    // 错误!不加done的话,就算接口不存在,这个函数一样是通过的
    fetchTwoData().then(res => {
        expect(res.data).toEqual({
            success: true
        })
    })
    // 正确
    return fetchTwoData().then(res => {
        expect(res.data).toEqual({
            success: true
        })
    })
});

异步测试(二)

{% note info green %} 使用 Async/Await {% endnote %}

// fetch.test.js
//第三种,使用Async/Await
test('写法1', async () => {
    const res = await fetchThreeData();
    expect(res.data).toEqual({success:true})
});
test('写法2', async () => {
  // 断言,必须执行一次expect
  // 在使用catch处理异常的时候,必须需要使用断言,不然`catch`里面的代码是不会执行的
  expect.assertions(1);
  try {
    await fetchData();
  } catch (e) {
    expect(e).toMatch('error');
  }
});
test('写法3', async () => {
  await expect(fetchData()).resolves.toEqual({success:true});
  await expect(fetchData()).rejects.toMatch('error');
});