前言
这篇博客我们主要讲三个方面的东西,在组件中的方法的mock
,以及对组件中promise
这类异步请求的mock
,在中间我们会穿插一下对测试覆盖率报告的讲解。先说一下我们为什么需要测试覆盖率报告和对方法以及请求的模拟操作。首先coverage
的存在让我们对我们项目的测试程度有一个客观的认知,它把我们测试用例的覆盖范围进行了一个量化操作,并且让我们可以清晰地看到我们的代码是否被执行,执行了多少次,覆盖了多少个行,方法,if模块等等,这些都是我们需要参考的指标级的东西。但是我们不能完全依赖测试覆盖率来衡量我们项目是否已经被我们的测试用例完美覆盖,因为这份报告只要代码被运行,就会增加我们的覆盖率,简而言之就是即使我们没有去断言,代码只要被运行了,测试覆盖率报告也会把这部分计入到覆盖率中,这无疑对于我们来说是一个误判,我们无法得知我们的代码是否已经真实地被完美测试了,所以我们到最后还是需要对我们的测试用例有一个主观的判断,是否已经达到了我们的测试标准。
关于测试覆盖率报告的具体知识点推荐可以看一下阮一峰老师对于Istanbul这款插件的博客代码覆盖率工具 Istanbul 入门教程
接下来我们讨论一下模拟方法的重要性,首先上篇博客我们已经提到了现在的测试对象依旧是以公共组件为主,这部分的组件相对而言是比较稳定且益于测试的,编写这方面的测试代码的成本相对而言不会很高,并且有着不错的收益,变动的概率也不会很大。但是一旦我们深入到业务组件的测试中,我们就必须需要去模拟我们请求的各种状态去测试我们的数据流和DOM
反馈是否是一个正常的状态,这个就需要我们去模拟去研究如何mock
掉promise
整个异步操作。
最后是mock function
,为什么我们需要对方法进行模拟,因为在我们的项目开发中我们经常会需要一些callback
也就是在一个方法中对别的方法进行回调操作,但是我们是无法对一个真实的方法是否被调用进行断言的,所以我们需要先把那个期待被测试的方法优先mock
掉,这样就可以去调用jest
的中的方法去对这个方法是否被断言是否被调用。
Coverage 测试覆盖率报告
我们执行npm run test:e2e命令运行单元测试后,会得到一个测试覆盖率报告,在第一篇博客中我们已经在jest.config.js
中进行过相应配置了,首先会在我们的控制台得到一个命令行式的覆盖率报告,如图
这里我们可以看到每个spec
都被执行过一次了,如果遇到哪个用例中的断言不通过则会显示相应的it
中的名字,并且把断言所期望和收到的值都打印出来。
在所有用例都跑过之后,会在我们项目下生成一个coverage
目录,里面会对应我们每一个项目中的组件生成一个html
文件来标记我们的代码执行情况
我们点击一个组件进去看一下
我们这里以StartQlink.vue组件为例,可以看到我们这个组件的方法都已经被执行一遍了,行覆盖率达到了93.62%,并且可以看到我们的方法被执行了几次。
模拟Promise请求
这是一个非常让我头疼的一个点,在mock请求这个点上我花了很久的去研究如何mock
掉整个axios
的请求。这个教程还是有一点瑕疵的,不过总体上已经达到了功能上的需求,这里我主要参考了老外的一个针对React写的测试教程。Mocking Axios in Jest + Testing Async Functions,想看这篇教程需要科学上网。
接下来我们进入整体,在mock
整个axios
的异步请求前,我们主要依赖的方法为Jest
提供的jest.fn()
这个函数。
- 首先我们需要在tests文件下新建一个名为
__mocks__
的目录,注意名称必须是这个,否则无法被Jest
所识别到。 - 然后我们创建一个
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
,最后抛出这个模拟的模块
- 模拟请求
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)
})
})
})
})
这里我们首先引用我们完成mock
的mockAxios
模块后,在后面我们引入了我们准备好的模拟数据,由于我们这一次需要mock的是一个GET
请求,所以在用例中我们通过调用mockAxios.get.mockImplementationOnce
来return
一个Promise.resolve
返回我们需要的response body
,这里因为我没有去模拟reject
以及catch
情况,所以我们的控制台会有一个warning,这个瑕疵我之后再解决。
- 之后我们运行我们包含请求的方法,我们通过
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
这类状态管理库。