jest学习笔记
什么是单元测试
单元测试(unit testing),是指对软件中的最小可测试代码单元进行检查和验证,是针对于代码的一种测试方法,测试的是代码
单元测试的目的
- 单元测试的目的是把程序里每个独立的最小单元隔离开来,保证其的正确性,使得整个程序相对也更正确
- 保证每个单元代码的功能是正确的
- 用于判断某个特定条件(或者场景)下某个特定函数(单元)的行为是否正确
- 单元测试在整个软件开发过程中所占比例应是最大的:它是最有效率、能最早发现问题、最便于维护的一种测试方法
单元测试的基本概念
核心概念
- 被测试的对象是什么
- 要测试该对象的什么功能
- 实际得到的结果
- 期望的结果
- mock/spy
包含步骤
- 准备阶段:构造参数,创建 spy 等
- 执行阶段:用构造好的参数执行被测试代码
- 断言阶段:用实际得到的结果与期望的结果比较,以判断该测试是否正常
- 清理阶段:清理准备阶段对外部环境的影响,移除在准备阶段创建的 spy 等
为什么学jest
相较于其他测试框架,jest对react比较友好,同样是Facebook开发,如果是cra创建脚手架,那么默认集成jest。jest中内置强大的断言与 mock 功能;内置测试覆盖率统计功能;内置 Snapshot 机制。
虽然 Jest 官网介绍中多次 React,但实际上 Jest 并不是和 React 绑定的。你可以使用它测试任何 JavaScript 项目。
jest配置
安装:
npm install --save-dev jest
在package.js中配置
{
"scripts": {
"test": "jest"
}
}
//如果希望能够自动进行测试
{
"scripts": {
"test": "jest" --watchAll
}
}
运行:
npm run test
第一个jest
js文件
function add(a,b){
return a+b
}
function cheng(a,b){
return a*b
}
module.exports ={
add , cheng
}
test文件
const suanshu = require('./JestDemo.js')
const {add , cheng} = suanshu
test('add,3和5',()=>{
expect(add(3,5)).toBe(8)
})
test('乘法,3和5',()=>{
expect(cheng(3,5)).toBe(15)
})
终端
PASS ./JestDemo.test.js
√ add,3和5 (1 ms)
√ 乘法,3和5
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 0.439 s, estimated 1 s
Ran all test suites.
讲解:在js文件中写方法,在test中写测试,在终端里查看,如果测试正确就会出现√。其中test是测试函数,里面有两个参数。一个是写要做什么事(描述),工作的时候尽量写英文,平时可以写中文。第二个参数是回调函数,里面写expect(期望)期望里是js文件夹里的函数,和要测试的数据。toBe是匹配器的Api,期望的值是多少,如果期望值和调用函数产生的值相等,那么就测试成功,否则失败。
JEST 更多配置
jest 配置项初始化
npx jest --init
√ Would you like to use Typescript for the configuration file? ... no
√ Choose the test environment that will be used for testing » jsdom (browser-like)
√ Do you want Jest to add coverage reports? ... yes
√ Which provider should be used to instrument code for coverage? » babel
√ Automatically clear mock calls, instances, contexts and results before every test? ... yes
查看代码覆盖率
npx jest --coverage
---------------------------------------------------
PASS ./JestDemo.test.js
√ add,3和5 (1 ms)
√ 乘法,3和5 (1 ms)
-------------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s -------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
JestDemo.js | 100 | 100 | 100 | 100 |
-------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 0.574 s, estimated 1 s
Ran all test suites.
匹配器
匹配器就是期望的结果存放的地方
-
toBe相当于绝对相等,一点不一样都不行
test('测试tobe匹配器',()=>{ const a = {number:007} expect(a).toBe( {number:007}) }) //引用类型不一样,所以是 failed,不能通过 test('测试tobe匹配器',()=>{ const a = {number:'007'} expect(a).toBe( {number:'007'}) }) //可以通过 -
toEqual为值相等,数据类型不用相等
test('toEqual',()=>{ const a = {number:007} expect(a).toEqual( {number:007}) }) //可以通过 -
toBeNull和toBeUndefined和toBeDefined 如果是null则匹配成功
test('测试toBeNull',()=>{ const a ; expect(a).toBeNull() }) //可以通过,因为a没有值 test('测试toBeUndefined',()=>{ expect(a).toBeUndefined() }) //可以通过,因为a没有被定义 test('测试toBeDefined',()=>{ const a = 1 expect(a).toBeDefined() }) //可以通过,只要被定义了,就可以 //最常用的是tobe和toEqual -
判断真假值 toBeTruthy 和 toBeFalsy
test('测试真假值',()=>{ const a = null expect(a).toBeTruthy () }) //不通过,因为null不是一个真值 test('测试真假值',()=>{ const a = 1 expect(a).toBeTruthy () }) //通过 test('测试真假值',()=>{ const a = null expect(a).toBeFalsy() }) //通过 test('测试真假值',()=>{ const a = 100 expect(a).toBeFalsy() }) //不通过 -
大于,小于,大于等于,小于等于
//大于,输出的值大于期望值才能通过 test('大于',()=>{ const a = 11 expect(a).toBeGreaterThan(10) }) //小于,输出的值必须小于期望值才能通过 test('小于',()=>{ const a = 11 expect(a).toBeLessThan(10) }) //大于等于 test('大于等于',()=>{ const a = 10 expect(a).toBeGreaterThanOrEqual(10) }) //小于等于 test('小于等于',()=>{ const a = 10 expect(a).toBeLessThanOrEqual(10) }) -
toBeCloseTo解决浮点数相加的问题
test('解决浮点数相加的问题',()=>{
const a = 0.1
const b = 0.2
expect(a).toBeEqual(0.3)
})
//错误,
test('解决浮点数相加的问题',()=>{
const a = 0.1
const b = 0.2
expect(a).toBeCloseTo(0.3)
})
//成功
-
toMatch 是否包含
test('判断是否包含',()=>{ const str = '2021-01-03' expect(a).toMatch ('3') }) //通过 test('判断是否包含',()=>{ const str = '2021-01-03' expect(a).toMatch ('5') }) //不通过 //只要里面包含就可以通过 -
toContain判断数组中是否包含某一项
test('判断数组中是否包含期望值',()=>{ const arr = ['1','2','3','4'] expect(a).toContain ('4') }) //通过
异步请求
-
异步请求promise的方法
---------------------------js------------------------------ import axios from "axios"; const fetchData = ()=>axios.get('https://api.apiopen.top/api/sentences') export { fetchData } ---------------------------test------------------------------ import {fetchData} from './jest-yibu' test('异步测试1',()=>{ return fetchData().then( (res)=>{ expect(res.data.message).toBe('成功!') } ) }) //重点一定要加return 或者是 async await -
使用 async await 测试
---------------------------js------------------------------
import axios from "axios";
const fetchData = ()=>axios.get('https://api.apiopen.top/api/sentences')
export {
fetchData
}
---------------------------test------------------------------
import {fetchData} from './jest-yibu'
test('异步测试2',async()=>{
const res = await fetchData()
expect(res.data.message).toBe('成功!')
})
生命周期
--------------------test-----------------------------
//如果想在执行**所有**测试用例之前执行某个函数可以这样
beforeAll(()=>{
return fn1();
})
//如果想在执行**完**所有测试用例之后执行某个函数可以这样
afterAll(()=>{
return fn2();
})
-----------------------------------------------------
//如果想在**每一个**测试用例之前都执行某个函数
beforeEach(()=>{
return fn3()
})
//如果想在每一个测试用例完成后都执行某个函数
afterEach(()=>{
return fn4()
})
---------------------------------------------------------
test('测试的函数1',()=>{
expect(fnTest1()).toBe('测试1')
})
test('测试的函数2',()=>{
expect(fnTest2()).toBe('测试2')
})
---------------------------------终端---------------------------------
//执行结果
fn1
fn3
fnTest1
fn4
fn3
fnTest2
fn4
fn2
分组
使用describe给相关联的测试用例进行分组,形成一个作用域
---------------------------------------test---------------------------------------
//现在有6个测试用例,可以按照单元,对其进行分组
test('一单元101测试',()=>{
expect(fnOne101()).toBe('一单元101')
})
test('一单元201测试',()=>{
expect(fnOne201()).toBe('一单元201')
})
test('一单元301测试',()=>{
expect(fnOne301()).toBe('一单元301')
})
test('二单元101测试',()=>{
expect(fnTwo101()).toBe('二单元101')
})
test('二单元201测试',()=>{
expect(fnTwo201()).toBe('二单元201')
})
test('二单元301测试',()=>{
expect(fnTw301()).toBe('二单元301')
})
---------------------------------------分组结果-------------------------------------
describe("一单元",()=>{
test('101测试',()=>{
expect(fnOne101()).toBe('一单元101')
})
test('201测试',()=>{
expect(fnOne201()).toBe('一单元201')
})
test('301测试',()=>{
expect(fnOne301()).toBe('一单元301')
})
afterEach(()=>{
return fn1()
})
})
describe("二单元",()=>{
test('101测试',()=>{
expect(fnTwo101()).toBe('二单元101')
})
test('201测试',()=>{
expect(fnTwo201()).toBe('二单元201')
})
test('301测试',()=>{
expect(fnTwo301()).toBe('二单元301')
})
beforEach(()=>{
return fn2()
})
})
-----------------------------------终端结果----------------------------------------------
fnOne101
fn1
fnOne201
fn1
fnOne301
fn1
fn2
fnTwo101
fn2
fnTwo201
fn2
fnTwo301
在describe中的生命周期钩子函数只会在此作用域中执行,而且,同分组的会一起执行,分组里可以再套一个分组。综上,可以总结出钩子函数作用域的规则
- 钩子函数在父级分组可作用域子集,类似继承
- 钩子函数同级分组作用域互不干扰,各起作用
- 先执行外部的钩子函数,再执行内部的钩子函数
模拟函数 MOCK
为了不影响其他函数对此函数的影响,依赖的函数建议以模拟的方式出现。尤其是第三方库等。
jest.spyOn(utils, 'getUUID').mockReturnValue('fake-uuid');
test('生成固定 id', () => {
expect(utils.getUUID()).toBe('fake-uuid');
});
这个部分牵扯到的东西较多也较难,在这里只举示例,了解为什么需要mock。
小技巧
一、 only 只执行这一个调试的时候常用
test.only('测试这一个',()=>{
expect(fn()).toBe('测试')
})
React+Vite+Ts配置Jest
安装
npm i jest ts-jest @types/jest @testing-library/react @testing-library/jest-dom @babel/preset-env @babel/preset-react @babel/preset-typescript -D
创建文件
path: jest.config.js
创建文件jest.config.js
export default {
// 使用ts-jest预设,这个预设包含了处理TypeScript文件所需的所有配置
preset: "ts-jest",
// 设置测试环境为jsdom,jsdom模拟了一个浏览器环境,允许在Node环境下运行浏览器特定的API
// testEnvironment: 'jsdom',
testEnvironment: "jest-environment-jsdom",
// 在每个测试文件运行之后,立即执行指定的脚本文件,这里是项目根目录下的src/setupTests.ts
// 通常用于全局的测试设置,比如配置enzyme或jest-dom等
setupFilesAfterEnv: ["<rootDir>/src/setupTests.ts"],
// 配置文件转换规则,告诉Jest如何处理项目中的不同类型的文件
transform: {
// 使用ts-jest处理.ts和.tsx文件
// 这允许Jest理解TypeScript语法并将其转换为JavaScript
"^.+\\.(ts|tsx)$": "ts-jest",
// 使用babel-jest处理.js和.jsx文件
// 这允许Jest通过Babel转换这些文件,支持ES6语法和React JSX
"^.+\\.(js|jsx)$": "babel-jest",
},
// 指定Jest在转换过程中应该忽略的文件模式
// 这里配置为忽略node_modules目录下的所有文件,这些文件通常不需要转换
transformIgnorePatterns: ["<rootDir>/node_modules/"],
moduleNameMapper: {
"\\.(css|less|scss|sass)$": "jest-transform-stub",
"\\.(jpg|jpeg|png|gif|webp|svg)$": "<rootDir>/__mocks__/fileMock.ts",
},
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
};
创建文件babel.config.ts
module.exports = {
presets: [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript",
],
};
创建文件src/setupTests.ts
import "@testing-library/jest-dom";
添加代码
package.json
"scripts": {
"test": "jest"
},
tsconfig.json
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"jsx": "react-jsx",
"outDir": "./dist",
"moduleResolution": "node",
"types": [
"jest"
],
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
},
"include": [
"src"
]