学习内容来源:Jest入门到TDD/BDD双实战_前端要学的测试课
相对原教程,我在学习开始时(2023.08)采用的是当前最新版本:
项 | 版本 |
---|---|
@babel/core | ^7.16.0 |
@pmmmwh/react-refresh-webpack-plugin | ^0.5.3 |
@svgr/webpack | ^5.5.0 |
@testing-library/jest-dom | ^5.17.0 |
@testing-library/react | ^13.4.0 |
@testing-library/user-event | ^13.5.0 |
babel-jest | ^27.4.2 |
babel-loader | ^8.2.3 |
babel-plugin-named-asset-import | ^0.3.8 |
babel-preset-react-app | ^10.0.1 |
bfj | ^7.0.2 |
browserslist | ^4.18.1 |
camelcase | ^6.2.1 |
case-sensitive-paths-webpack-plugin | ^2.4.0 |
css-loader | ^6.5.1 |
css-minimizer-webpack-plugin | ^3.2.0 |
dotenv | ^10.0.0 |
dotenv-expand | ^5.1.0 |
eslint | ^8.3.0 |
eslint-config-react-app | ^7.0.1 |
eslint-webpack-plugin | ^3.1.1 |
file-loader | ^6.2.0 |
fs-extra | ^10.0.0 |
html-webpack-plugin | ^5.5.0 |
identity-obj-proxy | ^3.0.0 |
jest | ^27.4.3 |
jest-enzyme | ^7.1.2 |
jest-resolve | ^27.4.2 |
jest-watch-typeahead | ^1.0.0 |
mini-css-extract-plugin | ^2.4.5 |
postcss | ^8.4.4 |
postcss-flexbugs-fixes | ^5.0.2 |
postcss-loader | ^6.2.1 |
postcss-normalize | ^10.0.1 |
postcss-preset-env | ^7.0.1 |
prompts | ^2.4.2 |
react | ^18.2.0 |
react-app-polyfill | ^3.0.0 |
react-dev-utils | ^12.0.1 |
react-dom | ^18.2.0 |
react-refresh | ^0.11.0 |
resolve | ^1.20.0 |
resolve-url-loader | ^4.0.0 |
sass-loader | ^12.3.0 |
semver | ^7.3.5 |
source-map-loader | ^3.0.0 |
style-loader | ^3.3.1 |
tailwindcss | ^3.0.2 |
terser-webpack-plugin | ^5.2.5 |
web-vitals | ^2.1.4 |
webpack | ^5.64.4 |
webpack-dev-server | ^4.6.0 |
webpack-manifest-plugin | ^4.0.2 |
workbox-webpack-plugin | ^6.4.1" |
具体配置、操作和内容会有差异,“坑”也会有所不同。。。
一、Jest 前端自动化测试框架基础入门
- 一、Jest 前端自动化测试框架基础入门(一)
- 一、Jest 前端自动化测试框架基础入门(二)
- 一、Jest 前端自动化测试框架基础入门(三)
- 一、Jest 前端自动化测试框架基础入门(四)
二、Jest难点进阶
- 二、Jest难点进阶(一)
- 二、Jest难点进阶(二)
3.mock timers
接下来学习一下对定时器的模拟
新建 Jest\src\lesson11\index.js
export default (cbk) => {
setTimeout(() => {
cbk()
}, 3000)
}
新建 Jest\src\lesson11_tests_\index.test.js
import timer from "../index";
test('测试 timer', () => {
timer(() => {
expect(2).toEqual(1)
})
})
执行测试用例,竟然成功了。。由于timer是一个异步函数,jest不会等cbk函数执行完毕,在cbk挂起期间没有明显问题直接就会返回成功
给它加点料,编辑 Jest\src\lesson11_tests_\index.test.js
import timer from "../index";
test('测试 timer', (done) => {
timer(() => {
expect(2).toEqual(1)
done()
})
})
执行测试用例报错,这才对嘛(改为 expect(2).toEqual(1)
后会成功)
使用了
done()
,这时候测试用例就会等done()
执行完毕出结果,但是若时间设置较长,这样的等待显然是不合理的
接下来进入正题,模拟 timer
编辑 Jest\src\lesson11_tests_\index.test.js(模拟 timer
后换一种测试方式)
import timer from "../index";
jest.useFakeTimers()
test('测试 timer', () => {
const fn = jest.fn();
timer(fn);
expect(fn).toHaveBeenCalledTimes(1)
})
使用
jest.useFakeTimers()
模拟 timer
执行测试用例报错,信息如下(确实模拟了timer
,但是没有执行)
Expected number of calls: 1
Received number of calls: 0
编辑 Jest\src\lesson11_tests_\index.test.js(执行模拟的 timer
)
import timer from "../index";
jest.useFakeTimers()
test('测试 timer', () => {
const fn = jest.fn();
timer(fn);
jest.runAllTimers()
expect(fn).toHaveBeenCalledTimes(1)
})
执行测试用例成功!
接下来看看其他相关用法
编辑 Jest\src\lesson11\index.js(定时器里面再放入一层,并执行 cbk
函数)
export default (cbk) => {
setTimeout(() => {
cbk()
setTimeout(() => {
cbk()
}, 3000)
}, 3000)
}
这时再运行之前的测试用例就通不过了,因为 runAllTimers
后,cbk
执行了两次
如何测试时只运行当前已触发的定时器呢?(运行代码时,只有最外层定时器加入队列,即触发)
编辑 Jest\src\lesson11_tests_\index.test.js(runAllTimers
改为 runOnlyPendingTimers
)
import timer from "../index";
jest.useFakeTimers()
test('测试 timer', () => {
const fn = jest.fn();
timer(fn);
jest.runOnlyPendingTimers()
expect(fn).toHaveBeenCalledTimes(1)
})
执行测试用例成功!
很显然只有这两个在日常测试是不够用的,接下来尝试另一个函数
编辑 Jest\src\lesson11_tests_\index.test.js(runOnlyPendingTimers
改为 advanceTimersByTime
)
import timer from "../index";
jest.useFakeTimers()
test('测试 timer', () => {
const fn = jest.fn();
timer(fn);
jest.advanceTimersByTime(3000)
expect(fn).toHaveBeenCalledTimes(1)
})
执行测试用例成功!
advanceTimersByTime
相当于是时间快进器,测试用例中在 3000 这个节点 fn 执行第一次,在 6000 这个节点 fn 执行第二次,因此在另外几个时间段的执行结果便呼之欲出了
当然 advanceTimersByTime
可以使用多次,不过需要注意的是,下一次使用是在上一次”快进“的基础上再次”快进“的
编辑 Jest\src\lesson11_tests_\index.test.js(多次使用advanceTimersByTime
)
import timer from "../index";
jest.useFakeTimers()
test('测试 timer', () => {
const fn = jest.fn();
timer(fn);
jest.advanceTimersByTime(3000)
expect(fn).toHaveBeenCalledTimes(1)
jest.advanceTimersByTime(3000)
expect(fn).toHaveBeenCalledTimes(2)
})
执行测试用例成功!
但是多个测试用例之间 advanceTimersByTime 会有相互影响
编辑 Jest\src\lesson11\index.js(在之前定时器里面再放入一层的基础上,再放入一层,并再执行 cbk
函数,一共三层,最终执行三次)
export default (cbk) => {
setTimeout(() => {
cbk()
setTimeout(() => {
cbk()
setTimeout(() => {
cbk()
}, 3000)
}, 3000)
}, 3000)
}
编辑 Jest\src\lesson11_tests_\index.test.js(在多个测试用例中使用advanceTimersByTime
)
import timer from "../index";
jest.useFakeTimers()
describe('测试 timer', () => {
const fn = jest.fn();
timer(fn);
test('第一次测试 timer', () => {
jest.advanceTimersByTime(3000)
expect(fn).toHaveBeenCalledTimes(1)
})
test('第二次测试 timer', () => {
jest.advanceTimersByTime(3000)
expect(fn).toHaveBeenCalledTimes(1)
})
test('第三次测试 timer', () => {
jest.advanceTimersByTime(3000)
expect(fn).toHaveBeenCalledTimes(1)
})
})
执行测试用例成功!(0~3 3~6 6~9 各执行一次)
调整参数:
import timer from "../index";
jest.useFakeTimers()
describe('测试 timer', () => {
const fn = jest.fn();
timer(fn);
test('第一次测试 timer', () => {
jest.advanceTimersByTime(2000)
expect(fn).toHaveBeenCalledTimes(0)
})
test('第二次测试 timer', () => {
jest.advanceTimersByTime(4000)
expect(fn).toHaveBeenCalledTimes(2)
})
test('第三次测试 timer', () => {
jest.advanceTimersByTime(3000)
expect(fn).toHaveBeenCalledTimes(1)
})
})
执行测试用例成功!(0~2 没有执行 2~6 执行两次 6~9 执行一次)
调整参数:
import timer from "../index";
jest.useFakeTimers()
describe('测试 timer', () => {
const fn = jest.fn();
timer(fn);
test('第一次测试 timer', () => {
jest.advanceTimersByTime(2000)
expect(fn).toHaveBeenCalledTimes(0)
})
test('第二次测试 timer', () => {
jest.advanceTimersByTime(2000)
expect(fn).toHaveBeenCalledTimes(1)
})
test('第三次测试 timer', () => {
jest.advanceTimersByTime(5000)
expect(fn).toHaveBeenCalledTimes(2)
})
})
执行测试用例成功!(0~1 没有执行 1~2 没有执行 2~9 执行三次)
从这三次测试调整中可以发现,toHaveBeenCalledTimes 统计的是每个测试用例里的 fn 调用次数,而 advanceTimersByTime 之间从前往后是相互叠加的
若是想要隔离这种影响,可以使用钩子函数
编辑 Jest\src\lesson11_tests_\index.test.js
import timer from "../index";
beforeEach(() => {
jest.useFakeTimers();
})
describe('测试 timer', () => {
test('第一次测试 timer', () => {
const fn = jest.fn();
timer(fn);
jest.advanceTimersByTime(2000)
expect(fn).toHaveBeenCalledTimes(0)
})
test('第二次测试 timer', () => {
const fn = jest.fn();
timer(fn);
jest.advanceTimersByTime(2000)
expect(fn).toHaveBeenCalledTimes(1)
})
test('第三次测试 timer', () => {
const fn = jest.fn();
timer(fn);
jest.advanceTimersByTime(5000)
expect(fn).toHaveBeenCalledTimes(2)
})
})
执行测试用例,只有第一个成功!成功隔离(这里虚晃一枪,请看到最后)
注意使用了钩子函数之后,只有在测试用例中调用的定时器才是经过mock的!
来个烧脑的,编辑 Jest\src\lesson11_tests_\index.test.js(将 fn
的定义放在外边)
import timer from "../index";
beforeEach(() => {
jest.useFakeTimers();
})
describe('测试 timer', () => {
const fn = jest.fn();
test('第一次测试 timer', () => {
timer(fn);
jest.advanceTimersByTime(2000)
expect(fn).toHaveBeenCalledTimes(0)
})
test('第二次测试 timer', () => {
timer(fn);
jest.advanceTimersByTime(2000)
expect(fn).toHaveBeenCalledTimes(1)
})
test('第三次测试 timer', () => {
timer(fn);
jest.advanceTimersByTime(5000)
expect(fn).toHaveBeenCalledTimes(2)
})
})
执行测试用例,前两个成功!最后一个:
Expected number of calls: 2
Received number of calls: 5
执行过程:
0~2
第一个函数执行,第一个定时器触发但没有执行完 02~4
第一个函数执行,第一个定时器执行完,第二个定时器触发但没有执行完 1
第二个函数执行,,第一个定时器触发但没有执行完 04~9
第一个函数执行,第二个和第三个定时器执行完 2
第二个函数执行,第一个和第二个定时器执行完 2
第三个函数执行,第一个定时器执行完 1
并没有视频课程中所说的隔离。。。但是功能理解了,over
本文仅作记录, 实战要点待后续专文总结,敬请期待。。。