使用 Vitest 测试 Vue.js 2 项目

643 阅读2分钟

介绍

Vitest 是一个原生支持 Vite 的测试框架。非常快速!

Vue Test Utils 是 Vue.js 官方的单元测试实用工具库。

安装相关依赖需要注意,一般默认最新版本是针对 Vue 3 的,对于 Vue 2 需要声明版本:

  • @vue/test-utils 需要安装 1.x 版本,2.x 是针对 Vue 3 的

  • 需要安装 @vitejs/plugin-vue2,而不是 Vue 3 的 Vite 插件 @vitejs/plugin-vue

# 安装所需依赖
npm install vite vitest @vue/test-utils@1 @vitejs/plugin-vue2 jsdom --save-dev

我测试的项目是 Vue CLI 3,安装 vite 仅用于测试,与 Vue CLI 3 不冲突。

Vitest 是由 Vite 驱动的下一代测试框架。Vitest 旨在将自己定位为 Vite 项目的首选测试框架,即使对于不使用 Vite 的项目也是一个可靠的替代方案。

Vitest 1.0 需要 Vite >= 5.0.0 和 Node >= 18

  • @vue/test-utils@1

Vue Test Utils 是官方的偏底层的组件测试库,它是为用户提供对 Vue 特定 API 的访问而编写的。

另一个选择 @testing-library/vue,不过我更喜欢用 Vue Test Utils。

  • @vitejs/plugin-vue2

Vite plugin for Vue 2.7

用于解析 .vue 文件。

  • jsdom

Vitest 中的默认测试环境是一个 Node.js 环境。如果要测试 Web 端应用,可以使用 jsdom 或 happy-dom 这种类似浏览器(browser-like)的环境来替代 Node.js。

先安装 jsdom 依赖,然后在配置项中设置 environment: 'jsdom'

使用

  1. 配置文件 vitest.config.js
import vue from '@vitejs/plugin-vue2'
import { defineConfig } from 'vitest/config'

export default defineConfig({
  plugins: [vue()],
  test: {
    environment: 'jsdom' // 默认值: 'node'
  }
})
  1. 运行命令 package.json
  "scripts": {
    "test": "vitest --config ./vitest.config.js"
  },
  1. 测试 Vue 组件 Comp.vue
<template>
  <div>
    <input type="text" v-model="bbb" />
    <p>{{ aaa }}: {{ bbb }}</p>
  </div>
</template>

<script>
export default {
  name: 'Comp',
  components: {},
  props: {
    aaa: String
  },
  data() {
    return {
      bbb: ''
    }
  },
  watch: {
    bbb() {
      this.$emit('modify')
    }
  }
}
</script>
  1. 执行测试 comp.test.js

一般情况下,执行测试的文件名中必须包含 ".test." 或 ".spec." 。

import { expect, test, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { nextTick } from 'vue'
import Component from './Comp.vue'

test('test Comp', async () => {
  const handleModify = vi.fn()

  // 使用 mount 创建一个包含被挂载和渲染的 Vue 组件的 Wrapper,
  // 该 Wrapper 对象包含了一个挂载的组件或 vnode,以及测试该组件或 vnode 的方法。
  const wrapper = mount(Component, {
    propsData: {
      aaa: '输入内容'
    },
    listeners: {
      modify: handleModify
    }
  })

  // getComponent: 返回第一个匹配的 Vue 组件的 Wrapper。
  // 如果未匹配到给定的选择器时会抛出错误。
  const inputWrapper = wrapper.getComponent('input')

  // 设置一个文本输入框的值并更新 v-model 绑定的数据。
  // 以下三种方式均可行:

  // 1: value & trigger
  // inputWrapper.element.value = '123'
  // trigger 会返回一个 Promise,当这个 Promise 被解决时,会确保组件已经被更新。
  // await inputWrapper.trigger('input')

  // 2: 使用原生的 dispatchEvent
  // inputWrapper.element.value = '123'
  // await inputWrapper.element.dispatchEvent(new Event('input'))

  // 3: 使用 setValue
  // setValue 用于设置一个文本控件或 select 元素的值并更新 v-model 绑定的数据。
  await inputWrapper.setValue('123')
  // 是接下来这段代码的别名
  // inputWrapper.element.value = '123'
  // await inputWrapper.trigger('input')

  // 任何导致操作 DOM 的改变都应该在断言之前 await nextTick 函数。
  // 在更新响应式 property 之后,我们可以直接 await 类似 trigger 或 trigger.vm.$nextTick 方法,等待 Vue 完成 DOM 更新。
  // 从 vue 引入 nextTick
  await nextTick()
  // 或者从 wrapper.vm 拿 $nextTick
  // await wrapper.vm.$nextTick()

  expect(wrapper.find('p').element.textContent).toEqual('输入内容: 123')
  expect(handleModify).toBeCalled()
})

image.png

参考

Vitest - expect

Vue Test Utils - mount

Vue Test Utils - wrapper

Vue Test Utils - 使用 nextTick 与 await

element-plus - input.test.tsx