【实战】二、Jest难点进阶(三) —— 前端要学的测试课 从Jest入门到TDD BDD双实战(七),Jest难点进阶实战指南,从入门到TDD与BDD双实战(七)

马肤

温馨提示:这篇文章已超过469天没有更新,请注意相关的内容是否还可用!

摘要:,,本文介绍了Jest的进阶难点,针对前端开发者要学习的测试课程,从Jest入门到TDD(测试驱动开发)和BDD(行为驱动开发)双实战的第七部分。文章深入探讨了Jest的复杂应用场景,帮助开发者解决在实际操作中遇到的难题,提升测试技能水平。

文章目录

    • 一、Jest 前端自动化测试框架基础入门
    • 二、Jest难点进阶
      • 3.mock timers

        学习内容来源:Jest入门到TDD/BDD双实战_前端要学的测试课

        【实战】二、Jest难点进阶(三) —— 前端要学的测试课 从Jest入门到TDD BDD双实战(七),Jest难点进阶实战指南,从入门到TDD与BDD双实战(七) 第1张
        (图片来源网络,侵删)

        相对原教程,我在学习开始时(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入门到TDD BDD双实战(七),Jest难点进阶实战指南,从入门到TDD与BDD双实战(七) 第2张
        (图片来源网络,侵删)

        一、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

                    第一个函数执行,第一个定时器触发但没有执行完 0

                    2~4

                    第一个函数执行,第一个定时器执行完,第二个定时器触发但没有执行完 1

                    第二个函数执行,,第一个定时器触发但没有执行完 0

                    4~9

                    第一个函数执行,第二个和第三个定时器执行完 2

                    第二个函数执行,第一个和第二个定时器执行完 2

                    第三个函数执行,第一个定时器执行完 1

                    并没有视频课程中所说的隔离。。。但是功能理解了,over


                    本文仅作记录, 实战要点待后续专文总结,敬请期待。。。


0
收藏0
文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。

相关阅读

  • 【研发日记】Matlab/Simulink自动生成代码(二)——五种选择结构实现方法,Matlab/Simulink自动生成代码的五种选择结构实现方法(二),Matlab/Simulink自动生成代码的五种选择结构实现方法详解(二)
  • 超级好用的C++实用库之跨平台实用方法,跨平台实用方法的C++实用库超好用指南,C++跨平台实用库使用指南,超好用实用方法集合,C++跨平台实用库超好用指南,方法与技巧集合
  • 【动态规划】斐波那契数列模型(C++),斐波那契数列模型(C++实现与动态规划解析),斐波那契数列模型解析与C++实现(动态规划)
  • 【C++】,string类底层的模拟实现,C++中string类的模拟底层实现探究
  • uniapp 小程序实现微信授权登录(前端和后端),Uniapp小程序实现微信授权登录全流程(前端后端全攻略),Uniapp小程序微信授权登录全流程攻略,前端后端全指南
  • Vue脚手架的安装(保姆级教程),Vue脚手架保姆级安装教程,Vue脚手架保姆级安装指南,Vue脚手架保姆级安装指南,从零开始教你如何安装Vue脚手架
  • 如何在树莓派 Raspberry Pi中本地部署一个web站点并实现无公网IP远程访问,树莓派上本地部署Web站点及无公网IP远程访问指南,树莓派部署Web站点及无公网IP远程访问指南,本地部署与远程访问实践,树莓派部署Web站点及无公网IP远程访问实践指南,树莓派部署Web站点及无公网IP远程访问实践指南,本地部署与远程访问详解,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南。
  • vue2技术栈实现AI问答机器人功能(流式与非流式两种接口方法),Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法探究,Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法详解
  • 发表评论

    快捷回复:表情:
    评论列表 (暂无评论,0人围观)

    还没有评论,来说两句吧...

    目录[+]

    取消
    微信二维码
    微信二维码
    支付宝二维码