【实战】一、Jest 前端自动化测试框架基础入门(四) —— 前端要学的测试课 从Jest入门到TDD BDD双实战(四)

【实战】一、Jest 前端自动化测试框架基础入门(四) —— 前端要学的测试课 从Jest入门到TDD BDD双实战(四)

码农世界 2024-02-23 后端 112 次浏览 1个评论

文章目录

    • 一、Jest 前端自动化测试框架基础入门
      • 10.Jest 中的 Mock
        • (1)toBeCalled
        • (2)func.mock
        • (3)mockReturnValue & mockReturnValueOnce

          学习内容来源: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 前端自动化测试框架基础入门(三)

          10.Jest 中的 Mock

          新建 lesson8.js

          export const runCallback = callback => {
            callback();
          }
          

          要测试 runCallback 一般思路都是创建一个函数,返回一个值,只要 expect 最终拿到这个值就说明没问题,但是 runCallback 的主要功能是执行 callback,若是和本示例这样没有将 callback 返回,岂不是测不了了。。。

          因此最佳实践来了,如下。

          (1)toBeCalled

          新建 lesson8.test.js

          import { runCallback } from "./lesson8";
          test('测试 runCallback', () => {
            const func = jest.fn();
            runCallback(func);
            expect(func).toBeCalled();  
          })
          

          mock 一个函数,使用 toBeCalled 即可检测函数是否被调用

          (2)func.mock

          打印看一下 func.mock 里面有啥(console.log(func.mock)):

          {
            calls: [ [] ],
            instances: [ undefined ],
            invocationCallOrder: [ 1 ],
            results: [ { type: 'return', value: undefined } ]
          }
          
          • calls:func 每次执行的入参列表的列表
          • instances:每次执行 func 时创建的实例的列表(即 函数中this的指向,默认 undefined)
          • invocationCallOrder:每次 func 执行的顺序列表(例如:1, 2, 3 表示按顺序执行)
          • results:每次执行 func 的返回值列表

          编辑 lesson8.test.js(执行两次 func)

          import { runCallback } from "./lesson8";
          test('测试 runCallback', () => {
            const func = jest.fn();
            runCallback(func);
            runCallback(func);
            expect(func).toBeCalled();  
            console.log(func.mock)
          })
          

          再看一下 func.mock 里面有啥:

          {
            calls: [ [], [] ],
            instances: [ undefined, undefined ],
            invocationCallOrder: [ 1, 2 ],
            results: [
              { type: 'return', value: undefined },
              { type: 'return', value: undefined }
            ]
          }
          

          可以看到每一次调用在 func.mock 中都是留有痕迹的

          编辑 lesson8.test.js(给 func 传入一个函数,并执行三次)

          test('测试 runCallback', () => {
            const func = jest.fn(() => 123); // mock 函数,捕获函数的调用
            // func.mockImplementation(() => 123); // 功能同上一句
            // func.mockImplementationOnce(() => 123); // 只模拟一次
            // func.mockImplementationOnce(() => 456); // 只模拟一次
            // func.mockImplementation(() => this);
            // func.mockReturnThis(); // 作用同上一句
            runCallback(func);
            runCallback(func);
            runCallback(func);
            expect(func).toBeCalled();
            // expect(func).toBeCalledWith(); // 断言每次调用的入参内容
            console.log(func.mock)
          })
          

          打印结果:

          {
            calls: [ [], [], [] ],
            instances: [ undefined, undefined, undefined ],
            invocationCallOrder: [ 1, 2, 3 ],
            results: [
              { type: 'return', value: 123 },
              { type: 'return', value: 123 },
              { type: 'return', value: 123 }
            ]
          }
          

          注意:带 Once 的要优先于不带的执行

          (3)mockReturnValue & mockReturnValueOnce

          编辑 lesson8.test.js(让 func 通过 mockReturnValueOnce 来返回值)

          test('测试 runCallback', () => {
            const func = jest.fn(); // mock 函数,捕获函数的调用
            func.mockReturnValueOnce('once')
            runCallback(func);
            runCallback(func);
            runCallback(func);
            expect(func).toBeCalled();
            console.log(func.mock)
          })
          

          打印结果:

          {
            calls: [ [], [], [] ],
            instances: [ undefined, undefined, undefined ],
            invocationCallOrder: [ 1, 2, 3 ],
            results: [
              { type: 'return', value: 'once' },
              { type: 'return', value: undefined },
              { type: 'return', value: undefined }
            ]
          }
          

          可以看到 'once' 只返回了一次

          编辑 lesson8.test.js(让 func 通过 mockReturnValueOnce, mockReturnValueOnce 链式调用,mockReturnValue 三种方式来返回值)

          test('测试 runCallback', () => {
            const func = jest.fn(); // mock 函数,捕获函数的调用
            func.mockReturnValueOnce('1');
            func.mockReturnValueOnce('2');
            func.mockReturnValueOnce('3').mockReturnValueOnce('4').mockReturnValueOnce('5');
            func.mockReturnValue('6'); // 后续每次返回同样的值
            [...new Array(8)].map(() => runCallback(func))
            expect(func).toBeCalled();
            console.log(func.mock)
          })
          

          打印结果:

          {
            calls: [
              [], [], [], [],
              [], [], [], []
            ],
            instances: [
              undefined, undefined,
              undefined, undefined,
              undefined, undefined,
              undefined, undefined
            ],
            invocationCallOrder: [
              1, 2, 3, 4,
              5, 6, 7, 8
            ],
            results: [
              { type: 'return', value: '1' },
              { type: 'return', value: '2' },
              { type: 'return', value: '3' },
              { type: 'return', value: '4' },
              { type: 'return', value: '5' },
              { type: 'return', value: '6' },
              { type: 'return', value: '6' },
              { type: 'return', value: '6' }
            ]
          }
          

          接下来通过示例理解一下 mock 里的 instances

          编辑 lesson8.js(callback 运行一次,之后创建一个实例,并赋予一个属性作为标识)

          export const runCallback = (callback, index) => {
            callback();
            let obj = new callback()
            obj.name = 'callback_' + index
          }
          

          编辑 lesson8.test.js

          test('测试 runCallback', () => {
            const func = jest.fn(); // mock 函数,捕获函数的调用
            func.mockReturnValue('6');  // 后续每次返回同样的值
            [...new Array(3)].map((item, index) => runCallback(func, index))
            expect(func).toBeCalled();
            console.log(func.mock)
          })
          

          打印结果:

          {
            calls: [ [], [], [], [], [], [] ],
            instances: [
              undefined,
              mockConstructor { name: 'callback_0' },
              undefined,
              mockConstructor { name: 'callback_1' },
              undefined,
              mockConstructor { name: 'callback_2' }
            ],
            invocationCallOrder: [ 1, 2, 3, 4, 5, 6 ],
            results: [
              { type: 'return', value: '6' },
              { type: 'return', value: undefined },
              { type: 'return', value: '6' },
              { type: 'return', value: undefined },
              { type: 'return', value: '6' },
              { type: 'return', value: undefined }
            ]
          }
          

          可见每次运行默认不会有实例化对象,因此是 undefined,但是在实例化后,就会有一个命名为 mockConstructor 的构造对象

          普通函数可以通过 jest.fn() 的方式来 mock,为保证当前部分功能的独立性,那接口请求也是需要 mock 的(避免网络和后端的影响)

          编辑 lesson8.js(新增 getData 请求接口)

          export const getData = () => {
            return axios.get('/api').then(res => res.data)
          }
          

          编辑 lesson8.test.js

          import { runCallback, getData } from "./lesson8";
          import axios from 'axios'
          jest.mock('axios');
          ...
          test.only('测试 getData', async () => {
            axios.get.mockResolvedValue({data: 'hello'})
            await getData().then(data => {
              expect(data).toBe('hello')
            })
          })
          

          注意:jest.mock 必须放在文件的上面紧贴着 import,且外面不能嵌套任何内容否则对其mock的内容的作用域有影响(与被mock内容保持一致,且加载顺序挨着)

          总结一下 mock 的几大功能:

          1. 捕获函数的调用和返回结果,以及 this 和调用顺序
          2. 可以自由设置返回结果
          3. 改变函数的内部实现

          注意,从25版本开始,官方文档的结构发生了一些变化,最后一个变化前的版本:

          • https://archive.jestjs.io/docs/en/24.x/api

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

转载请注明来自码农世界,本文标题:《【实战】一、Jest 前端自动化测试框架基础入门(四) —— 前端要学的测试课 从Jest入门到TDD BDD双实战(四)》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (有 1 条评论,112人围观)参与讨论
网友昵称:访客
访客游客 沙发
24-07-08 回复
今天是个特别的日子,值得纪念!http://6of9v.carteclcst.com/
Top