单元测试(三)—— Jest Mock组件方法及axios promise请求

本文深入探讨了测试覆盖率报告的解读及其与测试用例的关系,同时详细介绍了如何使用Jest进行方法和Promise请求的Mock操作,以提升业务组件的测试效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

这篇博客我们主要讲三个方面的东西,在组件中的方法的mock,以及对组件中promise这类异步请求的mock,在中间我们会穿插一下对测试覆盖率报告的讲解。先说一下我们为什么需要测试覆盖率报告和对方法以及请求的模拟操作。首先coverage的存在让我们对我们项目的测试程度有一个客观的认知,它把我们测试用例的覆盖范围进行了一个量化操作,并且让我们可以清晰地看到我们的代码是否被执行,执行了多少次,覆盖了多少个行,方法,if模块等等,这些都是我们需要参考的指标级的东西。但是我们不能完全依赖测试覆盖率来衡量我们项目是否已经被我们的测试用例完美覆盖,因为这份报告只要代码被运行,就会增加我们的覆盖率,简而言之就是即使我们没有去断言,代码只要被运行了,测试覆盖率报告也会把这部分计入到覆盖率中,这无疑对于我们来说是一个误判,我们无法得知我们的代码是否已经真实地被完美测试了,所以我们到最后还是需要对我们的测试用例有一个主观的判断,是否已经达到了我们的测试标准。
关于测试覆盖率报告的具体知识点推荐可以看一下阮一峰老师对于Istanbul这款插件的博客代码覆盖率工具 Istanbul 入门教程
接下来我们讨论一下模拟方法的重要性,首先上篇博客我们已经提到了现在的测试对象依旧是以公共组件为主,这部分的组件相对而言是比较稳定且益于测试的,编写这方面的测试代码的成本相对而言不会很高,并且有着不错的收益,变动的概率也不会很大。但是一旦我们深入到业务组件的测试中,我们就必须需要去模拟我们请求的各种状态去测试我们的数据流和DOM反馈是否是一个正常的状态,这个就需要我们去模拟去研究如何mockpromise整个异步操作。
最后是mock function,为什么我们需要对方法进行模拟,因为在我们的项目开发中我们经常会需要一些callback也就是在一个方法中对别的方法进行回调操作,但是我们是无法对一个真实的方法是否被调用进行断言的,所以我们需要先把那个期待被测试的方法优先mock掉,这样就可以去调用jest的中的方法去对这个方法是否被断言是否被调用。

Coverage 测试覆盖率报告

我们执行npm run test:e2e命令运行单元测试后,会得到一个测试覆盖率报告,在第一篇博客中我们已经在jest.config.js中进行过相应配置了,首先会在我们的控制台得到一个命令行式的覆盖率报告,如图
命令行报告
这里我们可以看到每个spec都被执行过一次了,如果遇到哪个用例中的断言不通过则会显示相应的it中的名字,并且把断言所期望和收到的值都打印出来。
在所有用例都跑过之后,会在我们项目下生成一个coverage目录,里面会对应我们每一个项目中的组件生成一个html文件来标记我们的代码执行情况
coverage测试报告总览
我们点击一个组件进去看一下
组件coverage
我们这里以StartQlink.vue组件为例,可以看到我们这个组件的方法都已经被执行一遍了,行覆盖率达到了93.62%,并且可以看到我们的方法被执行了几次。

模拟Promise请求

这是一个非常让我头疼的一个点,在mock请求这个点上我花了很久的去研究如何mock掉整个axios的请求。这个教程还是有一点瑕疵的,不过总体上已经达到了功能上的需求,这里我主要参考了老外的一个针对React写的测试教程。Mocking Axios in Jest + Testing Async Functions,想看这篇教程需要科学上网。
接下来我们进入整体,在mock整个axios的异步请求前,我们主要依赖的方法为Jest提供的jest.fn()这个函数。

  1. 首先我们需要在tests文件下新建一个名为__mocks__的目录,注意名称必须是这个,否则无法被Jest所识别到。
  2. 然后我们创建一个axios.ts的文件,在里面写入如下代码
const mockAxios: any = jest.genMockFromModule('axios');

// this is the key to fix the axios.create() undefined error!
mockAxios.create = jest.fn(() => mockAxios);
mockAxios.get = jest.fn(() => Promise.resolve({ data: {} }));
mockAxios.post = jest.fn(() => Promise.resolve({ data: {} }));
mockAxios.put = jest.fn(() => Promise.resolve({ data: {} }));
mockAxios.delete = jest.fn(() => Promise.resolve({ data: {} }));
mockAxios.all = jest.fn(() => Promise.resolve());

export default mockAxios;

在这段代码中我们首先需要mock掉整个axios模块,然后我们需要对axios中的create方法进行一个mock对我们的各种类型的方法进行一个模拟,我们这里统一采用了promise中的resolve场景,并且指定一个空的data,最后抛出这个模拟的模块

  1. 模拟请求
import Vue from 'vue'
import { shallowMount } from '@vue/test-utils';

import StartQLink from '@/views/Steps/views/StartQLink/StartQLink.vue'

import mockAxios from '../__mocks__/axios'
import { qlinks } from '../__mocks__/startQlink'

describe('StartQLink.vue', () => {
 it('获取QLink', () => {
   const wrapper = shallowMount(StartQLink)
   const vm: any = wrapper.vm
   mockAxios.get.mockImplementationOnce(() => {
     return Promise.resolve({
       data: {
         data: qlinks,
         error_code: 0,
         message: '',
       },
     })
   })
   vm.getQlinks()
   Vue.nextTick(() => {
     Vue.nextTick(() => {
       expect(vm.qlinks).toEqual(qlinks)
     })
   })
 })
})

这里我们首先引用我们完成mockmockAxios模块后,在后面我们引入了我们准备好的模拟数据,由于我们这一次需要mock的是一个GET请求,所以在用例中我们通过调用mockAxios.get.mockImplementationOncereturn一个Promise.resolve返回我们需要的response body,这里因为我没有去模拟reject以及catch情况,所以我们的控制台会有一个warning,这个瑕疵我之后再解决。

  1. 之后我们运行我们包含请求的方法,我们通过vm实例访问组件中的function,因为这是两次异步的操作,在方法调用后还有一次promise的异步请求,所以我们这里使用了两次Vue.nextTick来包裹我们最终的断言,在这个方法中我对请求回来的数据直接赋值到了data中,所以我在断言中直接访问了组件实例上的qlinks字段并且使用了toEqual方法去判断是否和我准备的数据相等,可以看到我们上面的测试报告中显示我们的代码已经被执行了,并且断言是一个通过的状态,说明我们的请求已经被成功模拟了

Mock方法

在上面我们完成了对Promise请求的模拟,我们的需求中还有可能对方法进行回调的断言,这里我也演示一下,先上代码。

  it('创建表空间', () => {
    const wrapper = shallowMount(CreateDB)
    const vm: any = wrapper.vm
    const mockFn = jest.fn()
    wrapper.setData({
      dbs: dbsMock,
      currentDB: 'test',
    })
    wrapper.setMethods({
      getTabs: mockFn,
    })
    mockAxios.post.mockImplementationOnce(() => {
      return Promise.resolve({
        data: {
          data: {},
          error_code: 0,
          message: '',
        },
      })
    })
    vm.handlePostCreateTab()
    Vue.nextTick(() => {
      Vue.nextTick(() => {
        expect(mockFn).toHaveBeenCalled()
        expect(vm.openAddTabModal).toBeFalsy()
      })
    })
  })

在以上这段代码中我们使用const mockFn = jest.fn()创建了一个假的方法,然后通过vue-test-utils中的setMethods去替代了原先需要被回调判断的方法getTabs,之后我们在断言中直接使用toHaveBeenCalled判断这个方法是否被调用就可以了。当然也可以使用其他的方法去判断这个方法具体被执行了多少次,这里就不赘述了。

总结

这一篇我们着重讲解了测试用例和测试覆盖率报告的结合使用以及对方法和请求的Mock操作,这是测试业务组件和测试用例撰写的一个非常重要的点,有了这些基础功能后使得我们对后面组件更深入的测试有了一个更加光明的遐想空间,为提升我们的测试覆盖率以及我们的断言质量也有了一个非常好的铺垫。在下一个章节我将会去研究如何测试setTimeout以及Vuex这类状态管理库。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值