单元测试
单元测试是开发应用程序的一个重要(有时被忽略)部分。它们有助于我们的开发流程,确保我们项目的最关键部分不受开发过程中意外错误或疏忽的影响。因此,Vue有自己的测试程序 vue-test-utils。它提供了与 Vue 组件交互的功能,并与许多流行的测试框架一起使用。
Vuetify 使用 Typescript,目前必须导入和继承 Vue 对象。在某些应用程序中,这可能会生成一个 $attrs 或 $listeners 只读警告。目前有一个正在进行的 Github 讨论,为各种测试用例提供潜在的解决方案。如果你还有额外的问题,请加入我们的 在线社区。
测试环境设置
关于如何使用 Vue CLI 设置测试运行程序的信息可以在 官方文档 中找到。一目了然,Vue CLI 为以下测试运行程序提供了入门资源库。
引导 Vuetify
为了正确使用 Typescript,Vuetify 组件继承了 Vue 对象。这可能 导致问题 。我们必须在单元测试设置文件中全局安装 Vuetify,而不是使用 localVue instance。这在测试运行程序之间可能有所不同。请务必参考有关安装文件的适当文档。
// test/setup.js
import Vue from 'vue'
import Vuetify from 'vuetify'
Vue.use(Vuetify)
如果您没有使用 setup.js
文件,则应在测试的程序部分中添加 Vue.use(Vuetify)
。
规范测试
在 Vuetify 中创建类似于 vuex 和 vue-router 的单元测试,因为您将在localVue 实例中使用 Vuetify 对象,并将实例传递给 mount 函数。
让我们创建一个在应用程序中的测试用例示例。
<!-- Vue Component -->
<template>
<v-card>
<v-card-title>
<span v-text="title" />
<v-spacer></v-spacer>
<v-btn @click="$emit('action-btn:clicked')">
Action
</v-btn>
</v-card-title>
<v-card-text>
<slot />
</v-card-text>
</v-card>
</template>
<script>
export default {
props: {
title: {
type: String,
required: true,
},
},
}
</script>
在上面的例子中,我们创建了一个自定义组件 title prop 和 v-btn
,当单击它时,它触发一个自定义事件。现在,我们想要创建测试以确保该行为正常工作,并且在将来的更改中也能这样运行。下面的示例是使用 Jest 测试环境创建的。
// test/CustomCard.spec.js
// Libraries
import Vue from 'vue'
import Vuetify from 'vuetify'
// Components
import CustomCard from '@/components/CustomCard'
// Utilities
import {
mount,
createLocalVue
} from '@vue/test-utils'
const localVue = createLocalVue()
describe('CustomCard.vue', () => {
let vuetify
beforeEach(() => {
vuetify = new Vuetify()
})
it('should have a custom title and match snapshot', () => {
const wrapper = mount(CustomCard, {
localVue,
vuetify,
propsData: {
title: 'Foobar',
},
})
// With jest we can create snapshot files of the HTML output
expect(wrapper.html()).toMatchSnapshot()
// We could also verify this differently
// by checking the text content
const title = wrapper.find('.v-card__title > span')
expect(title.text()).toBe('Foobar')
})
it('should emit an event when the action v-btn is clicked', () => {
const wrapper = mount(CustomCard, {
localVue,
vuetify,
propsData: {
title: 'Foobar',
},
})
const event = jest.fn()
const button = wrapper.find('.v-btn')
// Here we bind a listener to the wrapper
// instance to catch our custom event
// https://vuejs.org/v2/api/#Instance-Methods-Events
wrapper.vm.$on('action-btn:clicked', event)
expect(event).toHaveBeenCalledTimes(0)
// Simulate a click on the button
button.trigger('click')
// Ensure that our mock event was called
expect(event).toHaveBeenCalledTimes(1)
})
})
如果你遇到有关测试或者一般问题并且需要帮助,请访问我们的 在线社区。
测试效率
在编写测试时,您经常会发现自己在重复同样的事情。在这种情况下,创建助手函数来减少每个测试的重复。DRYing
在单元测试中编写的最常见的重复代码之一是 mount 函数。这可以很容易地编写为可重用函数。
// test/CustomCard.spec.js
describe('CustomCard.vue', () => {
const mountFunction = options => {
return mount(CustomCard, {
localVue,
vuetify,
...options,
})
}
it('should have a custom title and match snapshot', () => {
const wrapper = mountFunction({
propsData: {
title: 'Fizzbuzz',
},
})
expect(wrapper.html()).toMatchSnapshot()
})
})
Mocking Vuetify
Vuetify 的许多组件都使用全局 $vuetify
对象来生成默认文本或断点信息等设置。在测试这些组件时,需要使用一个模拟对象来提供 vue-test-utils
。
// test/CustomAlert.spec.js
// Libraries
import Vue from 'vue'
import Vuetify from 'vuetify'
// Components
import CustomAlert from '@/components/CustomAlert'
// Utilities
import {
mount,
createLocalVue
} from '@vue/test-utils'
const localVue = createLocalVue()
describe('CustomAlert.vue', () => {
let vuetify
beforeEach(() => {
vuetify = new Vuetify({
mocks: {
$vuetify: {
lang: {
t: (val: string) => val,
},
},
}
})
})
it('should have a custom title and match snapshot', () => {
const wrapper = mount(CustomAlert, {
localVue,
vuetify,
})
expect(wrapper.html()).toMatchSnapshot()
})
})
请记住,你 只需要存根 正在使用的服务。例如 lang 或application。除此之外你也可以手动导入这些服务。
// test/CustomNavigationDrawer.spec.js
// Libraries
import Vue from 'vue'
import Vuetify from 'vuetify'
// Components
import CustomNavigationDrawer from '@/components/CustomNavigationDrawer'
// Utilities
import {
createLocalVue,
mount,
} from '@vue/test-utils'
const localVue = createLocalVue()
describe('CustomNavigationDrawer.vue', () => {
let vuetify
beforeEach(() => {
vuetify = new Vuetify()
})
it('should have a custom title and match snapshot', () => {
const wrapper = mount(CustomNavigationDrawer, {
localVue,
vuetify,
})
expect(wrapper.html()).toMatchSnapshot()
})
})
所有可用服务的完整列表如下:
E2E 测试
Vuetify 将 data-*
属性从组件传递给相关的 HTML 元素,这使得 E2E 测试框架能够轻松地定位它们。
例如,Cypress 建议添加 data-cy
属性,以便更容易地定位元素。
<template>
<!-- Vuetify component with data-cy -->
<v-text-field v-model="name" data-cy="name-input" />
<!-- HTML render output -->
<input data-cy="name-input" id="input-120" type="text">
</template>
// cypress/integration/test.spec.js
describe('Test With Attribute', () => {
it('Find by data-cy', () => {
cy.visit('/')
cy.get('[data-cy=name-input]').type('Zak') // Find element using data-cy
})
})