前端2021:vue3.0 VTU-Jest 之第一个单元测试

2,170 阅读4分钟

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 方法申明一个测试用例,并简短的表述当前测试用例。testexpect 函数在大多数测试运行程序中都是全局可用的。如果testexpect 仍然感到困惑不解,在 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

使用“data-test”选择器不是必需的,但它可以使您的测试不那么脆弱。class和id往往会随着应用程序的增长而改变或移动——通过使用“data-test”,其他开发人员可以清楚地知道哪些元素在测试中使用,不应该更改。 执行测试用例: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的异步方法前。比如triggersetValue。 将测试用例修改如下,将会正确通过:
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