单元测试是软件开发非常基础的一部分。单元测试会封闭执行最小化单元的代码,使得添加新功能和追踪问题更容易。Vue 的单文件组件使得为组件撰写隔离的单元测试这件事更加直接。它会让你更有信心地开发新特性而不破坏现有的实现,并帮助其他开发者理解你的组件的作用。
这是一个判断一些文本是否被渲染的简单的示例:
<template>
<div>
<input v-model="username">
<div
v-if="error"
class="error"
>
{{ error }}
</div>
</div>
</template>
<script>
export default {
name: 'Hello',
data () {
return {
username: ''
}
},
computed: {
error () {
return this.username.trim().length < 7
? 'Please enter a longer username'
: ''
}
}
}
</script>
import { shallowMount } from '@vue/test-utils'
import Hello from './Hello.vue'
test('Hello', () => {
// 渲染这个组件
const wrapper = shallowMount(Hello)
// `username` 在除去头尾空格之后不应该少于 7 个字符
wrapper.setData({ username: ' '.repeat(7) })
// 确认错误信息被渲染了
expect(wrapper.find('.error').exists()).toBe(true)
// 将名字更新至足够长
wrapper.setData({ username: 'Lachlan' })
// 断言错误信息不再显示了
expect(wrapper.find('.error').exists()).toBe(false)
})
上述代码片段展示了如何基于 username 的长度测试一个错误信息是否被渲染。它展示了 Vue 组件单元测试的一般思路:渲染这个组件,然后断言这些标签是否匹配组件的状态。
组件的单元测试有很多好处:
单元测试应该:
<template>
<div>
<div class="message">
{{ message }}
</div>
Enter your username: <input v-model="username">
<div
v-if="error"
class="error"
>
Please enter a username with at least seven letters.
</div>
</div>
</template>
<script>
export default {
name: 'Foo',
data () {
return {
message: 'Welcome to the Vue.js cookbook',
username: ''
}
},
computed: {
error () {
return this.username.trim().length < 7
}
}
}
</script>
我们应该测试的内容有:
import { shallowMount } from '@vue/test-utils'
import Foo from './Foo.vue'
describe('Foo', () => {
it('renders a message and responds correctly to user input', () => {
const wrapper = shallowMount(Foo, {
data: {
message: 'Hello World',
username: ''
}
})
// 确认是否渲染了 `message`
expect(wrapper.find('.message').text()).toEqual('Hello World')
// 断言渲染了错误信息
expect(wrapper.find('.error').exists()).toBeTruthy()
// 更新 `username` 并断言错误信息不再被渲染
wrapper.setData({ username: 'Lachlan' })
expect(wrapper.find('.error').exists()).toBeFalsy()
})
})
上述代码有一些问题:
import { shallowMount } from '@vue/test-utils'
import Foo from './Foo'
const factory = (values = {}) => {
return shallowMount(Foo, {
data () {
return {
...values
}
}
})
}
describe('Foo', () => {
it('renders a welcome message', () => {
const wrapper = factory()
expect(wrapper.find('.message').text()).toEqual("Welcome to the Vue.js cookbook")
})
it('renders an error when username is less than 7 characters', () => {
const wrapper = factory({ username: '' })
expect(wrapper.find('.error').exists()).toBeTruthy()
})
it('renders an error when username is whitespace', () => {
const wrapper = factory({ username: ' '.repeat(7) })
expect(wrapper.find('.error').exists()).toBeTruthy()
})
it('does not render an error when username is 7 characters or more', () => {
const wrapper = factory({ username: 'Lachlan' })
expect(wrapper.find('.error').exists()).toBeFalsy()
})
})
注意事项:
在一开始,工厂函数将 values 对象合并到了 data 并返回了一个新的 wrapper 实例。这样,我们就不需要在每个测试中重复 const wrapper = shallowMount(Foo)。另一个好处是当你想为更复杂的组件在每个测试中伪造或存根一个方法或计算属性时,你只需要声明一次即可。
上述的测试是非常简单的,但是在实际情况下 Vue 组件常常具有其它你想要测试的行为,诸如:
快照比对测试会保存你的 Vue 组件的标记,然后比较每次新生成的测试运行结果。如果有些东西改变了,开发者就会得到通知,并决定这个改变是刻意为之 (组件更新时) 还是意外发生的 (组件行为不正确)。
端到端测试致力于确保组件的一系列交互是正确的。它们是更高级别的测试,例如可能会测试用户是否注册、登录以及更新他们的用户名。这种测试运行起来会比单元测试和快照比对测试慢一些。
单元测试中开发的时候是最有用的,即能帮助开发者思考如何设计一个组件或重构一个现有组件。通常每次代码发生变化的时候它们都会被运行。