前言
作为程序员,没有比临上线前发现之前的更改的代码导致应用崩溃更糟糕的事了。而唯一能够确保应用正常的工作的只有进行程序的测试。所以说对应用进行全面的测试是很重要的一件事了。
高效的测试方法可以加快开发速度,提高代码质量,尽早发现并去除代码中的BUG
。测试驱动开发(TDD)
是一种在编写代码先编写测试代码的工作流程,即在编写代码前,得先编写能够确保组件正常运行的测试代码。
而单元测试是对应用程序最小的单元运行测试的过程。通常是函数,但在VUE
中,组件也是单元,因为组件的本质就是函数。
下面简单的说说如何使用JEST
进行VUE
的单元测试。如果不熟悉JEST
与VUE
,建议看看官网。
示例代码在github
组件的单元测试
单元是应用中最小的可测试部分。在VUE
中,组件与函数都是可测试的单元。
搭载测试环境
对于新项目来说,可以使用VUE-CLI
工具创建项目(版本如图所示)
只需要在选择配置时☑️Unit Testing
,并选择JEST
作为测试框架即可。
而对于现有项目而言(针对@vue/cli
)想增加jest
测试模块。运行以下命令行就会帮我们去安装jest
模块。
vue add unit-jest
如果你是一个动手能力较强的同学,还可以自己去搭建测试环境,可以参考Vue Test Utils
好了,现在可以进行单元测试了。
挂载组件
当导入一个VUE
组件时,它只是一个带有渲染函数和一些属性的对象或者函数。要测试组件行为,则首先得启动它并开启渲染过程。按照VUE
的说法就是需要挂载组件。
挂载组件,需要将组件选项转换为一个VUE
构造函数。而组件选项对象不是一个有效的的构造函数,它只是一个普通的JavaScript
对象。这时可以使用Vue.extend
方法从选项中创建一个VUE
构造函数:
import Vue from 'vue';
import Home from '@/views/Home.vue';
const Ctor = Vue.extend(Home);
这时,就可以使用new
操作符来创建一个实例:
const vm = new Ctor();
通常,VUE
使用el
选项在文档中查找添加的被渲染DOM
节点。但一般的组件构造函数并没有el
选项,因此在创建实例时,它不会自动挂载并生成DOM
节点,需要手动调用$mount
方法:
const vm = new Ctor().$mount()
当调用$mount
时,VUE
将生成一些DOM
节点,可以使用实例中$el
属性在测试中访问这些节点:
expect(vm.$el.textContent).toContain('Welcome to Your Vue.js App')
VUE
要使用DOM
方法创建一个DOM
树。这不意味着VUE
组件的单元测试必须在浏览器环境中运行。因为在默认情况下,Jest
会在jsdom
库创建的浏览器环境中运行测试。jsdom
是一个DOM
实现,它完全是由运行在DOM
中的JavaScript
编写的。
import Vue from 'vue';
import Home from '@/views/Home.vue';
describe('Home.vue', () => {
it('renders msg when mounted', () => {
const msg = 'Welcome to Your Vue.js App';
// 使用Home选项创建一个新的Vue构造函数
const Ctor = Vue.extend(Home);
// 创建一个新的Vue实例并挂载该实例
const vm = new Ctor().$mount();
// 访问DOM元素,检查文本内容
expect(vm.$el.textContent).toContain(msg)
})
})
运行yarn run test:unit
,测试通过
使用Vue Test Utils
挂载组件需要自己去创建构造函数并且手动挂载。与其自己编写这些代码,不如使用Vue Test Utils
库来帮忙实现。
Vue Test Utils
库会让Vue
组件单元测试变得更加简单。它包含一些辅助方法可以实现组件挂载、与组件交互以及断言组件输出。
Vue Test Utils
会导出一个mount
方法,该方法在接收一个组件后,会将其挂载并返回一个包含被挂载组件实例vm
的包装器对象。之所以会返回包装器对象而不直接返回vm
实例,是因为包装器不仅仅只有实例vm
,还包括一些辅助方法。其中一个方法就是text
,它返回实例的textContent
。
使用Vue Test Utils
库改造刚刚的测试
import { mount } from '@vue/test-utils';
import Home from '@/views/Home.vue';
describe('Home.vue', () => {
it('renders msg when mounted', () => {
const msg = 'Welcome to Your Vue.js App';
// 使用mount方法挂载组件
const wrapper = mount(Home);
// 检查文本内容
expect(wrapper.text()).toContain(msg)
})
})
使用shallowMount
除了mount
方法,Vue Test Utils
还包含一个shallowMount
方法。shallowMount
不会像mount
一样渲染整个组件树,它只渲染一层组件树
与
mount
相似,shallowMount
挂载一个组件并返回一个包装器。不同之处在于,shallowMount
在挂载组件之前对所有子组件进行存根。
shallowMount
可以确保对一个组件进行独立测试,有助于避免测试中因子组件的渲染输出而混乱结果。
describe('Home.vue', () => {
it('renders msg when mounted', () => {
const msg = 'Welcome to Your Vue.js App';
// 使用shallowMount方法挂载组件
const wrapper = shallowMount(Home);
// 检查文本内容
expect(wrapper.text()).toContain(msg)
})
})
输出测试
测试prop
为一个组件编写单元测试时,需要为组件提供生成环境中接收到的输入数据。如果组件在生产环境中接收一个prop
,需要在组件挂载到测试时为组件提供该prop
。
使用Vue Test Utils
将prop
作为一个选项对象传递给组件:
import { shallowMount } from '@vue/test-utils';
import Hello from '@/components/HelloWorld.vue';
describe('HelloWorld.vue', () => {
it('renders msg when mounted', () => {
const msg = 'hello, world';
// 使用shallowMount方法挂载组件
const wrapper = shallowMount(Hello, {
propsData: {
msg
}
});
// 检查文本内容
expect(wrapper.text()).toContain(msg)
})
})
运行yarn run test:unit
,测试通过!
包装器text
方法将返回组件渲染的所有文本。toContain
匹配器会检查一个值是否包含在它所检查的字符串中的某个位置,它有点像string.prorotype.includes
方法。
而像上面例子,props
中的msg
变量是渲染在<h1>
标签上的,没有必要去匹配组件下的全部文本,需要测试<h1>
标签上的文本是否匹配即可。这时可以使用包装器的find
方法。
find
方法可以为渲染输出中的每个节点获取包装器,而且find
会搜索与选择器匹配的第一个节点的渲染输出,并返回包含给匹配节点的包装器。
/ 检查文本内容
expect(wrapper.find('h1').text()).toContain(msg)
并不是所有prop
都渲染出文本。所以还要检查组件实例是否接收正确的prop
。这时可以使用props
方法。
props
方法是一个包装器方法。它返回一个对象,其中包含一个包装器组件实例及它们的值prop
。
expect(wrapper.props().msg).toBe(msg)
使用toBe
匹配器检查组件所有的渲染文本是否违反此原则。
还可以检查当前组件传值是否正确
const wrapper = shallowMount(fatherComponent)
expect(wrapper.find(ChildComponent).props().propA).toBe('example')
这里需要注意的是,如果一个组件未声明要接收一个prop
,则prop
不会被添加到这个Vue
实例中。
测试DOM属性
在Vue Test Utils
包装器中,有一个attribute
方法,可以返回组件属性对象。可以使用该对象来测试属性值
const a = wrapper.find('a');
expect(a.attributes().href === 'http://cli.vuejs.org').toBa(true)
错误信息显示:期望是
true
,返回的是false
。这是一个Boolean
断言,这样的信息并没有很清晰的了解到测试为什么会失败,所以应该要避免Boolean
断言。应该使用值断言代替它
这样就非常清楚是哪里的问题导致测试失败了。
测试样式
在Vue Test Utils
包装器中,有一个classes
方法,返回一个class
数组。可以对此进行断言,查看元素是否具有一个class
。
expect(a.classes()).toContain('firstA')
toContain
匹配器不仅可以检查一个字符串中是否包含另一个字符串,还能比较数组中的值。
通常来说测试内联样式是没有价值的,但有时候不得不去测试一个内联样式。比如说:进度条组件,动态地添加一个内联样式。这时就需要直接访问包装器元素并获取样式属性值。
每一个包装器都包含一个element
属性,它是对包装器包含的DOM
根节点的引用。可以使用它来访问内联元素
expect(a.element.style.fontSize).toBe('14px')
结尾
由于篇幅原因,这次测试之旅第一天就结束了,下次再继续。阅读完,如果觉得有帮助的请点点赞,支持一下。
更多文章请移步楼主github,如果喜欢请点一下star,对作者也是一种鼓励。