github:jest/unit 案例
让我们直接进入主题,通过构建一个简单的例子 Todo App
并编写测试用例来一步一步学习Vue Test Utils(VTU)
本小结将会覆盖如下:
- Mount 挂载组件
- Find 查找元素
- Fill 填写表单
- Trigger 触发事件
起步
首先搭建项目环境,并选择Unit Testing
,这里我们以vue3.0 Typescript
项目举例:
TodoApp.vue
<template>
<div>
<div
v-for="todo in todos"
:key="todo.id"
data-test="todo"
:class="[ todo.completed ? 'completed' : '' ]"
>
{{ todo.text }}
<input
type="checkbox"
v-model="todo.completed"
data-test="todo-checkbox"
/>
</div>
<form data-test="form" @submit.prevent="createTodo">
<input data-test="new-todo" v-model="newTodo" />
</form>
</div>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component';
@Options({
props: {
msg: String
}
})
export default class TodoApp extends Vue {
newTodo='';
todos=[
{
id: 1,
text: 'Learn Vue.js 3',
completed: false
}
]
createTodo() {
this.todos.push({
id: 2,
text: this.newTodo,
completed: false
})
}
}
</script>
第一个测试用例
首先我们编写第一个测试用例,验证 todo App
被渲染了,让我们先看一下测试用例,稍后将讨论每一部分:
完整 TodoApp.spec.js
import { mount } from '@vue/test-utils'
import TodoApp from '@/components/TodoApp.vue'
test('creates a todo', async () => {
const wrapper = mount(TodoApp)
expect(wrapper.findAll('[data-test="todo"]')).toHaveLength(1)
await wrapper.get('[data-test="new-todo"]').setValue('New todo')
await wrapper.get('[data-test="form"]').trigger('submit')
expect(wrapper.findAll('[data-test="todo"]')).toHaveLength(2)
})
test('completes a todo', async () => {
const wrapper = mount(TodoApp)
await wrapper.get('[data-test="todo-checkbox"]').setValue(true)
expect(wrapper.get('[data-test="todo"]').classes()).toContain('completed')
})
首先我们通过 import的方式 导入 mount
-这是在VTU中一个主要的渲染组件component 的方法。
你可以通过 test
方法申明一个测试用例,并简短的表述当前测试用例。test
和expect
函数在大多数测试运行程序中都是全局可用的。如果test
和 expect
仍然感到困惑不解,在 Jest 文档有更加简单明了的例子,来告诉你如何使用它们。
接下来,我们调用mount
并传入一个组件 component 作为第一个参数-这一步几乎是所有的测试用例都会写到的。按照惯例,我们将结果赋值给一个名为wrapper
的变量,因为mount
为应用提供了一个简单的"wrapper",并提供了一些方便的测试方法。比如 .find
、.get
等等
最后,我们使用另外一个对于大多数测试用例都通用的 Jest
方法 - expect
.它的主要作用是,让我们断言或期待,在这种测试用例情况下实际输出符合我们期望的结果。在这个测试用例中,我们通过选择器data-test="todo"
来查找 DOM 元素,如:<div data-test="todo">...</div>
。紧接着我们通过调用 call
方法得到 dom内容,也就是我们期望的结果Learn vue.js 3
执行测试用例:Pass
yarn test:unit
新增一个新的代办项
接下来我们需要丰富我们的测试用例,我们需要让用户新增创建一个代办项 new todo item
.因此我们需要一个form
表单,内嵌一个 input
输入框,允许用户来输入待办项。当用户点击提交submit
,我们期望的结果是新增待办项内容被渲染。让我们看一下以下测试用例:
import { mount } from '@vue/test-utils'
import TodoApp from '@/components/TodoApp.vue'
test('creates a todo', () => {
const wrapper = mount(TodoApp)
expect(wrapper.findAll('[data-test="todo"]')).toHaveLength(1)
wrapper.get('[data-test="new-todo"]').setValue('New todo')
wrapper.get('[data-test="form"]').trigger('submit')
expect(wrapper.findAll('[data-test="todo"]')).toHaveLength(2)
})
- 为了更新
<input>
,我们使用setValue
-该方法将使我们 input 的 value 值。 - 更新
<input>
后,我们通过trigger
方法,模拟用户操作提交表单的行为。 - 最后我们期望
todo items
的数量将由 1 增加至 2;
我们使用v-model
来动态绑定<input>
的value值,使用@submit
来监听表单的提交事件,当用户被提交,createTodo
将被调用,todos
数组将被插入一个新的代办对象;
让我们再次执行当前测试用例,发现如下错误:
我们期望的 todos 数组对象没有新增,Received length:1
;
原因:
- Jest 执行测试用例是同步执行的方式,只要用例的最后一个方法执行完毕,则当前用例结束测试。
- Vue 更新Dom是异步更新机制。
方案:
- 我们需要通过
async
标记测试用例方法,调用await
在那些可能改变Dom的异步方法前。比如trigger
、setValue
。
将测试用例修改如下,将会正确通过:
import { mount } from '@vue/test-utils'
import TodoApp from '@/components/TodoApp.vue'
test('creates a todo', async () => {
const wrapper = mount(TodoApp)
expect(wrapper.findAll('[data-test="todo"]')).toHaveLength(1)
await wrapper.get('[data-test="new-todo"]').setValue('New todo')
await wrapper.get('[data-test="form"]').trigger('submit')
expect(wrapper.findAll('[data-test="todo"]')).toHaveLength(2)
})
完成一个待办项
我们期望用户能够通过一个 checkbox
标记一个待办项是否已经完成的测试用例:
test('completes a todo', async () => {
const wrapper = mount(TodoApp)
await wrapper.get('[data-test="todo-checkbox"]').setValue(true)
expect(wrapper.get('[data-test="todo"]').classes()).toContain('completed')
})
分析测试用例:
- 我们通过
wrapper.get('[data-test="todo-checkbox")
来找到checkbox
元素; - 因为是
input
类型我们仍然通过setValue方法赋值; - 我们断言/期望我们将通过新增一个
class
类来标记完成状态的待办项。
总结:
- 使用
mount()
方法来挂载一个组件。 - 使用
get()
和findAll()
方法去查找 DOM。 trigger()
和setValue()
方法用来模拟用户input操作以及表单事件行为.- 更新
DOM
是异步操作,因此记得使用async
和await
。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!