在现代前端开发中,Vue.js 因其简洁和灵活性被广泛采用。而随着应用的复杂性增加,测试也变得越来越重要。Vue Test Utils(简称 VTU)是一个专为 Vue.js 设计的测试库,它提供了一系列功能强大的工具,使得开发者可以更方便地对 Vue 组件进行单元测试和集成测试。本文将详细探讨 VTU 的功能、使用方法以及其在 Vue.js 测试中的重要性。
VTU 的核心功能
VTU 提供了一套完整的 API,用于挂载和测试 Vue 组件。这些 API 主要分为以下几类:
1. 挂载组件
VTU 提供两个核心的挂载方法:mount 和 shallowMount。其中:
mount会完整地挂载组件及其子组件。适用于组件本身和子组件之间存在较多交互,需要整体进行测试的场景。shallowMount则仅挂载组件本身,而对子组件进行浅层的处理(即,用占位符代替子组件)。这种方式可以隔离组件,专注于组件内部的逻辑和状态,从而加快测试速度。
import { mount, shallowMount } from '@vue/test-utils';
import MyComponent from '@/components/MyComponent.vue';
const wrapper = mount(MyComponent);
const shallowWrapper = shallowMount(MyComponent);
2. 找到元素
VTU 提供了多种查找 DOM 元素的方法,例如 find、findAll、findComponent 和 findAllComponents 等。这些方法允许开发者通过 CSS 选择器、组件名称等来定位和操作组件的子元素或子组件。
const button = wrapper.find('button');
const paragraphs = wrapper.findAll('p');
const childComponent = wrapper.findComponent({ name: 'ChildComponent' });
不可见元素
使用 isVisible()
isVisible() 提供检查隐藏元素的能力。特别是 isVisible() 将检查是否:
- 元素或其祖先具有
display: none,visibility: hidden,opacity :0样式 - 元素或其祖先位于折叠
<details>标记内 - 元素或其祖先具有
hidden属性
使用 v-show 的测试场景如下所示:
test('does not show the user dropdown', () => {
const wrapper = mount(Nav)
expect(wrapper.get('#user-dropdown').isVisible()).toBe(false)
})
3. 事件模拟与事件处理
在测试中,我们常常需要模拟用户交互。VTU 提供了如 trigger 等一系列方法来模拟事件,使得测试更贴近实际应用场景。
await button.trigger('click');
Vue 组件通过 prop 和通过调用 $emit 发出事件来相互交互。在本指南中,我们将介绍如何使用 emitted() 函数验证事件是否正确发出。
const Counter = {
template: '<button @click="handleClick">Increment</button>',
data() {
return {
count: 0
}
},
methods: {
handleClick() {
this.count += 1
this.$emit('increment', this.count)
}
}
emitted() 方法。它返回一个对象,其中包含组件发出的所有事件,以及数组中的参数。让我们看看它是如何工作的:
test('emits an event when clicked', () => {
const wrapper = mount(Counter)
wrapper.find('button').trigger('click')
wrapper.find('button').trigger('click')
expect(wrapper.emitted()).toHaveProperty('increment')
})
我们需要检查在调用 this.$emit('increment', this.count) 时是否发出正确的参数。
我们的下一步是断言事件包含 count 值。我们通过向 emitted() 传递一个参数来实现这一点。
test('emits an event with count when clicked', () => {
const wrapper = mount(Counter)
wrapper.find('button').trigger('click')
wrapper.find('button').trigger('click')
// `emitted()` accepts an argument. It returns an array with all the
// occurrences of `this.$emit('increment')`.
const incrementEvent = wrapper.emitted('increment')
// We have "clicked" twice, so the array of `increment` should
// have two values.
expect(incrementEvent).toHaveLength(2)
// Assert the result of the first click.
// Notice that the value is an array.
expect(incrementEvent[0]).toEqual([1])
// Then, the result of the second one.
expect(incrementEvent[1]).toEqual([2])
})
断言复杂事件:
4. 表单处理
Vue 中的表单可以像纯 HTML 表单一样简单,也可以像自定义 Vue 组件表单元素的复杂嵌套树一样简单。我们将逐步了解与表单元素交互、设置值和触发事件的方式。我们最常使用的方法是 setValue() 和 trigger()。
要更改 VTU 中输入的值,您可以使用 setValue() 方法。它接受一个参数,通常是一个 String 或 Boolean,并返回一个 Promise,它在 Vue 更新 DOM 后解析。
test('sets the value', async () => {
const wrapper = mount(Component)
const input = wrapper.find('input')
await input.setValue('my@mail.com')
expect(input.element.value).toBe('my@mail.com')
})
正如您所看到的,setValue 将输入元素上的value 属性设置为我们传递给它的值。在我们做出任何断言之前,我们使用 await 来确保 Vue 已完成更新并且更改已反映在 DOM 中。setValue是一种非常通用的方法。它可以与所有类型的表单元素一起使用。
在上面的表单中,我们将事件从 button 移动到 form 元素。这是一个值得遵循的好习惯,因为这允许您通过点击 enter 键来提交表单,这是一种更原生的方法。为了触发 submit 处理程序,我们再次使用 trigger 方法。
test('submits the form', async () => {
const wrapper = mount(FormComponent)
const email = 'name@mail.com'
const description = 'Lorem ipsum dolor sit amet'
const city = 'moscow'
await wrapper.find('input[type=email]').setValue(email)
await wrapper.find('textarea').setValue(description)
await wrapper.find('select').setValue(city)
await wrapper.find('input[type=checkbox]').setValue()
await wrapper.find('input[type=radio][value=monthly]').setValue()
await wrapper.find('form').trigger('submit.prevent')
expect(wrapper.emitted('submit')[0][0]).toStrictEqual({
email,
description,
city,
subscribe: true,
interval: 'monthly'
})
})
测试复杂的输入组件:
如果您的输入组件不那么简单会发生什么?您可能正在使用 UI 库,例如 Vuetify。如果您依赖挖掘标记内部来查找正确的元素,那么如果外部库决定更改其内部结构,您的测试可能会中断。
In such cases you can set the value directly, using the component instance and setValue.在这种情况下,您可以使用组件实例和 setValue 直接设置值。
Assume we have a form that uses the Vuetify textarea:假设我们有一个使用 Vuetify 文本区域的表单:
vue视图
<template>
<form @submit.prevent="handleSubmit">
<v-textarea v-model="description" ref="description" />
<button type="submit">Send</button>
</form>
</template>
<script>
export default {
name: 'CustomTextarea',
data() {
return {
description: ''
}
},
methods: {
handleSubmit() {
this.$emit('submitted', this.description)
}
}
}
</script>
We can use findComponent to find the component instance, and then set its value.我们可以使用findComponent来查找组件实例,然后设置它的值。
jsjs
test('emits textarea value on submit', async () => {
const wrapper = mount(CustomTextarea)
const description = 'Some very long text...'
await wrapper.findComponent({ ref: 'description' }).setValue(description)
wrapper.find('form').trigger('submit')
expect(wrapper.emitted('submitted')[0][0]).toEqual(description)
})
5. 快照测试
快照测试是一种将组件的渲染输出进行快照记录的方法。VTU 与 Jest 等工具配合使用时,可以方便地进行快照测试,确保组件的 UI 没有意外变化。
expect(wrapper.html()).toMatchSnapshot();
6. 测试服务器端渲染
renderToString 是一个将 Vue 组件渲染为字符串的函数。它是一个异步函数,返回一个 Promise,并接受与 mount 或 shallowMount 相同的参数。
让我们考虑一个使用 onServerPrefetch 钩子的简单组件:
function fakeFetch(text: string) {
return Promise.resolve(text)
}
const Component = defineComponent({
template: '<div>{{ text }}</div>',
setup() {
const text = ref<string | null>(null)
onServerPrefetch(async () => {
text.value = await fakeFetch('onServerPrefetch')
})
return { text }
}
})
可以使用 renderToString 为此组件编写测试:
import { renderToString } from '@vue/test-utils'
it('renders the value returned by onServerPrefetch', async () => {
const contents = await renderToString(Component)
expect(contents).toBe('<div>onServerPrefetch</div>')
})
三、编写易于测试的组件
四、VTU 的优势
VTU 的诞生为 Vue.js 组件的测试带来了诸多优势:
- 简化测试流程:VTU 的 API 设计简洁且易于使用,极大地简化了测试流程,使得开发者可以专注于编写高质量的测试用例。
- 高效的测试执行:
shallowMount方法提供了高效的测试方式,避免了不必要的子组件渲染,从而加快了测试执行速度。 - 灵活的测试场景:VTU 提供了丰富的 API,可以满足各种测试需求,无论是简单的单元测试,还是复杂的集成测试。
- 社区支持:作为 Vue 官方推荐的测试工具,VTU 拥有强大的社区支持和不断更新的文档,使得开发者可以及时获取帮助和更新。
五、实践中的 VTU
在实际项目中,VTU 的应用主要体现在以下几个方面:
- 单元测试:通过单元测试,可以验证组件在隔离环境下的行为,例如方法调用、数据处理和事件响应。
- 集成测试:通过集成测试,可以验证多个组件之间的交互和数据流,例如父子组件的通信、Vuex 状态管理等。
- 快照测试:通过快照测试,可以确保组件的 UI 输出符合预期,防止出现意外变化。
以下是一个简单的单元测试示例:
import { shallowMount } from '@vue/test-utils';
import MyComponent from '@/components/MyComponent.vue';
describe('MyComponent', () => {
it('renders a message', () => {
const wrapper = shallowMount(MyComponent, {
propsData: { message: 'Hello World' }
});
expect(wrapper.text()).toContain('Hello World');
});
it('increments count when button is clicked', async () => {
const wrapper = shallowMount(MyComponent);
const button = wrapper.find('button');
await button.trigger('click');
expect(wrapper.vm.count).toBe(1);
});
});
总结
VTU 作为 Vue.js 的测试工具,为开发者提供了一套强大且易用的 API,使得组件测试变得更加高效和便捷。通过 VTU,开发者可以在提高代码质量、减少 bug 和提升用户体验等方面获得显著的成果。在未来的开发过程中,掌握和使用 VTU 将成为每一个 Vue.js 开发者的重要技能。希望本文能够帮助读者深入了解 VTU 的功能和使用方法,为 Vue.js 项目的测试提供有力支持。
Join the conversation
Discuss on GitHubSign in with GitHub to comment. Both language versions share this conversation.