基础知识
前期准备
npx jest --init初始化jest.config.js模块- 脚本
jest --watchAll启动监听修改。npx jest 文件路径,只测试单个文件。npx jest 文件名部分将匹配所有包含改部分的文件。npx jest -o只检查git/hg未提交的代码 - 匹配/^.+.test.js$/模块,以下方式可以扩展它
// jest.config.js
const {defaults} = require('jest-config');
module.exports = {
moduleFileExtensions: [...defaults.moduleFileExtensions, 'ts', 'tsx'],
// moduleFileExtensions: ["js", "jsx", "ts", "tsx", "json", "node"],
};
- 可以在package.json中写入脚步
"precommit": "jest"或
"husky": {
"hooks": {
"pre-commit": "npm run test"
}
},
pre表示后面执行的命令之前执行post表示后面执行的命令执行后执行,即precommit -> commit -> postcommit
常用语法
- 作用域
describe:(name: number | string | Function | FunctionLike, fn: EmptyFunction): void - 测试内容
it或test:(name: string, fn?: ProvidesCallback, timeout?: number): void;ProvidesCallback的第一个参数done方法表示当前微任务(个人测试)结束后当前测试任务结束,可以返回Promise 第三个参数是异步函数测试的超时时间
// 对ProvidesCallback参数的测试
// 由于异步代码it无法获知
const lazy = (fn)=> {
setTimeout(() => {
fn();
}, 3000);
}
it('ProvidesCallback', function (
// done // 注意,如果写了done就必须执行
) {
const callback = jest.fn();
lazy(callback);
// done()
// Promise.resolve().then(() => {
// expect(callback).toBeCalled();
// })
setTimeout(() => {
// done()
expect(callback).toBeCalled(); // 测试未执行至此当前测试就已退出
}, 1000);
// 以下代码也可正常通过测试,建议和assertions一起使用
// return new Promise(resolve => {
// setTimeout(() => {
// expect(callback).toBeCalled();
// resolve()
// }, 3001);
// })
})
describe.onle、it.onle、describe.skip、it.skip表示只运行或跳过。
expect: 用于校验测试是否通过的方法,常见有严格相等expect(1).toBe(1)、递归子项相等expect({a: 1}).toEqual({a: 1})、是否已赋值expect(null).toBeDefined()、不匹配expect().not.等beforeAll、afterAll、beforeEach、beforeEach:(fn: ProvidesCallback, timeout?: number) => any分别是当前作用域(由describe产生)所有测试开始前、所有测试结束后、每一个测试开始前、每一个测试结束后执行。执行顺序可以为beforeAll->beforeEach->beforeEach->beforeEach->beforeEach->...->afterAll。顺序外层before->内层before->内层after->外层afterexpect.assertions(number)表示当前测试需要经过多少个expect,否则测试不通过。主要用途是异步时是否按正常逻辑去执行了expectjest.fn():用于处理测试中创建的函数的执行次数或参数或返回值
it('jest.fn', function () {
const fn = jest.fn()
.mockReturnValue(1) // 声明返回值,也可以声明传参
let num = fn()
expect(num).toBe(1)
// 是否调用过
expect(fn).toBeCalled()
// 调用的次数
expect(fn).toBeCalledTimes(1)
})
语法使用场景:
- fetch中的回调方法可以穿入fn,测试fn是否执行
- react hooks中测试在生命周期中执行的正确性,ahooks中useUnmount就是用的这种方式测试
it('test unmount', async () => {
const fn = jest.fn();
const hook = renderHook(() => useUnmount(fn));
expect(fn).toBeCalledTimes(0);
hook.rerender();
expect(fn).toBeCalledTimes(0);
hook.unmount();
expect(fn).toBeCalledTimes(1);
});
jest.mock:作用类似jest.fn,不同之处在于jest.mock需要引入一个模块jest.mock('./a.js'),类似于将a中导出的函数都用jest.fn封装,于是我们可以记录a.js中函数被调用的次数
const a = requrie(./a.js) // a中有fn方法
const b = requrie(./b.js) // b方法调用了a.fn方法
jest.mock('./a.js')
it('jest.mock', function () {
b()
// 是否调用过
expect(a.fn).toHaveBeenCalled()
// 调用的次数
expect(a.fn).toHaveBeenCalledTimes(1)
})
jest.spyOn:对jest.mock的细化,接收两个参数,第一个参数是模块或对象,第二个参数是对象中的函数名
const a = requrie(./a.js) // a中有fn方法
const b = requrie(./b.js) // b方法调用了a.fn方法
it('jest.spyOn', function () {
b() // 此处调用不会计入次数
const fn = jest.spyOn(a, 'fn')
b()
// 是否调用过
expect(fn).toHaveBeenCalled()
// 调用的次数
expect(fn).toHaveBeenCalledTimes(1)
})
- 定时器相关
jest.useFakeTimers(): 告诉jest当前模块可以使用假时间,即可以控制时间快进jest.runAllTimers(): 让所有定时器立即执行jest.clearAllTimes(): 清除所有定时器jest.runOnlyPendingTimers(): 立即执行最快的定时器jest.advanceTimersByTime(): 让时间快进jest.advanceTimersByTime(3000)快进3s
在react中使用jest
react中常用的测试库
react-test-renderer,以及基于react-test-renderer的@testing-library/:主要有react、react-hooks(更推荐)
@testing-library/react-hooks
常用两个api: renderHook, act,最新版本还有cleanup, addCleanup, removeCleanup, suppressErrorOutput
- act 为react-test-renderer导出方法,修改状态的方法必须在该函数中调用
- renderHook renderHook函数接收一个函数和props,函数返回值为测试的hook调用后的结果,函数传参为之后rerender调用时的传参。
renderHook返回值主要有result、rerender、unmount、waitForNextUpdate。waitFor、waitForValueToChange
result.current即测试的hook调用后的结果
rerender方法可以改变hook的prop,无须在act中使用,内部已实现。unmount卸载后也可以用它重新挂载
unmount方法执行时当前hook卸载
waitForNextUpdate等待下一个状态更新,超时时间超过1s则测试不通过。主要作用是等待Promise模拟的接口调用后数据变更
// useMyHook
const [state, setState] = useState(1)
useEffect(() => {
api().then((num) => setState(num))
}, [])
return state
// test.js
const api = () => Promise.resolve(3)
let hook
// 此处后续在使用rerender时 await act 也可行,是因为微任务执行顺序
// 开发时请使用waitForNextUpdate
act(() => {
hook = renderHook((r = api) => useMyHook(r))
})
// 只有等待模拟数据来时才往下走
await hook.waitForNextUpdate()
expect(hook.result.current).toBe(3)
- cleanup方法用于清除缓存,防止内存泄露
@testing-library/react
- fireEvent 模拟用户操作 例如fireEvent.click(HtmlElement)
- render 加载组件,返回获取组件节点的方法,常用getByText、getByRole
// jest.config.*
import type { Config } from '@jest/types'
const config: Config.InitialOptions = {
preset: 'ts-jest/presets/js-with-ts',
clearMocks: true,
testEnvironment: 'jsdom',
modulePathIgnorePatterns: ['<rootDir>/package.json'],
moduleNameMapper: {
'@/(.*)$': '<rootDir>/src/$1'
},
rootDir: '.',
resetMocks: false,
globals: {
'ts-jest': {
tsconfig: 'tsconfig.json'
}
}
}
export default config
在npx create-react-app my-app中使用
react-scripts库内置了jest,可以直接使用
在其它中使用
npm install --save-dev jest babel-jest babel-preset-es2015 babel-preset-react react-test-renderer
// .babelrc
{
"presets": ["es2015", "react"]
}
特别注意Jest 版本 24 开始不再支持 Babel 6。推荐升级Babel 7。但是如果你无法升级到 Babel 7,可以继续使用 Jest 23,或者升级到 Jest 24 并且使 babel-jest 固定在 23 的版本
"dependencies": {
"babel-core": "^6.26.3",
"babel-jest": "^23.6.0",
"babel-preset-env": "^1.7.0",
"jest": "^24.0.0"
}
在vue中使用
使用vue-cli搭建,选择unit -> jest,注意在package.json中识别测试的文件规则为
[ "**/__tests__/*.{j,t}s?(x)", "**/tests/unit/**/*.spec.{j,t}s?(x)"]
// jest.config.*
import type { Config } from '@jest/types'
const config: Config.InitialOptions = {
preset: 'ts-jest',
clearMocks: true,
testEnvironment: 'jsdom',
modulePathIgnorePatterns: ['<rootDir>/package.json'],
roots: ['<rootDir>'],
transform: {
// 注意jest版本和vue版本,低版本使用vue-jest
// https://github.com/vuejs/vue-jest/issues/355
'^.+\.vue$': 'vue3-jest'
},
resetMocks: false,
moduleFileExtensions: ['ts', 'tsx', 'js', 'json', 'vue']
}
export default config