Vue2 应用测试学习 01 - Vue Test Utils 介绍和快速体验

本文主要是介绍Vue2 应用测试学习 01 - Vue Test Utils 介绍和快速体验,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Vue 默认安装版本已更新为 v3,本文使用 v2 学习 Vue 应用测试。

Vue 官方推荐了两个用于组件测试的框架:

  • Vue Test Utils:Vue 官方提供的测试库,进行单元测试很方便,当然也可以进行集成测试。
  • Vue Testing Library:更轻量的测试库,封装自 Vue Test Utils ,但只保留了进行集成测试的一些功能。

下面学习使用 Vue Test Utils

创建带有 Vue Text Utils 的 Vue 应用

官方介绍了手动在应用中集成测试工具的过程。

我们也可以使用 Vue CLI 创建自带测试工具的 Vue 应用,省略这些繁琐安装:

# 使用 Vue CLI 创建项目
npx @vue/cli create vue-testing-demo? Please pick a preset: Manually select features
# 勾选单元测试 Unit
? Check the features needed for your project: Babel, Router, Vuex, Linter, Unit
? Choose a version of Vue.js that you want to start the project with 2.x
? Use history mode for router? (Requires proper server setup for index fallback in production) No
? Pick a linter / formatter config: Standard
? Pick additional lint features: Lint on save, Lint and fix on commit
# 测试方案选择 Jest
? Pick a unit testing solution: Jest
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No

安装完成后,可以看到项目根目录下有个 tests 目录,里面有个存放单元测试的目录 unit,里面存放了一个单元测试文件 example.spec.js

内容是声明了一个 describe 块,导入了 HelloWorld 组件,通过 it() 创建了一个测试用例,测试组件渲染后的文本内容是否和期望的一样。

import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'describe('HelloWorld.vue', () => {it('renders props.msg when passed', () => {const msg = 'new message'const wrapper = shallowMount(HelloWorld, {propsData: { msg }})expect(wrapper.text()).toMatch(msg)})
})

npm run test:unit 运行测试脚本,可以看到测试结果。

Jest 默认配置说明

Vue CLI 使用一个预设配置 Jest:

// jest.config.js
module.exports = {preset: '@vue/cli-plugin-unit-jest'
}

实际配置来源于 node_modules\@vue\cli-plugin-unit-jest\presets\default\jest-preset.js

// node_modules\@vue\cli-plugin-unit-jest\presets\default\jest-preset.js
// ...module.exports = {testEnvironment: 'jsdom',// 指定 Jest 测试中加载模块时可以省略的后缀名moduleFileExtensions: ['js','jsx','json',// tell Jest to handle *.vue files'vue'],// 文件转换(Jest 默认只处理 js 文件)transform: {// process *.vue files with vue-jest'^.+\\.vue$': vueJest,'.+\\.(css|styl|less|sass|scss|jpg|jpeg|png|svg|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|avif)$':require.resolve('jest-transform-stub'),'^.+\\.jsx?$': require.resolve('babel-jest')},// 转换忽略的文件transformIgnorePatterns: ['/node_modules/'],// support the same @ -> src alias mapping in source code// 模块名称映射moduleNameMapper: {'^@/(.*)$': '<rootDir>/src/$1'},// serializer for snapshots// 快照的序列化器// vue 组件渲染的结果保存到快照前的序列化操作snapshotSerializers: ['jest-serializer-vue'],// 测试文件匹配规则testMatch: ['**/tests/unit/**/*.spec.[jt]s?(x)','**/__tests__/*.[jt]s?(x)'],// https://github.com/facebook/jest/issues/6766// 测试时提供给 jsdom 的基准环境路径(涉及 URL 相关测试)testURL: 'http://localhost/',// 监听相关的插件(提供了一些命令行的辅助工具)watchPlugins: [require.resolve('jest-watch-typeahead/filename'),require.resolve('jest-watch-typeahead/testname')]
}

快速体验

官方文档:教程 | Vue Test Utils

挂载组件&测试组件渲染的 HTML

Vue Test Utils 提供的 mountshallowMount (不渲染子组件)方法用来快速渲染挂载组件,省略了创建 DOM 节点,调用 render 渲染组件的过程。

挂载组件返回一个包裹器,包含很多封装、遍历和查询其内部 Vue 组件实例的便捷方法。

<!-- src\components\HelloWorld.vue -->
<template><div class="hello"><button @click="count += 1">Click</button><p data-testid="count-text">{{ count }}</p><h1>{{ msg }}</h1></div>
</template><script>
export default {name: 'HelloWorld',props: {msg: String},data () {return {count: 0}}
}
</script>
// tests\unit\example.spec.js
import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'test('HelloWorld.vue', () => {// 挂载组件,获得一个包裹器const wrapper = shallowMount(HelloWorld, {// 模拟 propspropsData: {msg: 'Hello World'}})// 组件实例console.log(wrapper.vm)// wrapper.element - 组件根节点console.log(wrapper.element.outerHTML)// wrapper 包含很多辅助方法,上面打印内容也可以写作:console.log(wrapper.html())// 检查是否包含指定字符串expect(wrapper.html()).toContain('Hello World')
})

模拟用户交互

test('点击按钮,count 为 1', () => {const wrapper = shallowMount(HelloWorld)// 注意:find 已废弃,将在未来版本删除,使用 findComponent 替换// const button = wrapper.find('button')const button = wrapper.findComponent('button')const countText = wrapper.findComponent('[data-testid="count-text"]')// 触发事件button.trigger('click')expect(wrapper.vm.count).toBe(1) // 测试成功expect(countText.text()).toBe('1') // 测试失败:实际内容是 '0'
})

等待 Vue 完成 DOM 更新

上面交互测试虽然 js 中的 wrapper.vm.count 已经更改,但是 DOM 中的内容并没有更新,这是因为 Vue 会对未生效的 DOM 进行批量异步更新,避免因数据反复变化而导致不必要的渲染。

所以任何导致操作 DOM 的改变,都应该在更新响应式属性之后,断言之前等待 Vue 完成 DOM 更新。

我们在 Vue 项目中经常使用 $nextTick 实例方法等待 DOM 更新完成,测试代码中也可以使用:

// 引入 Vue
import Vue from 'vue'test('点击按钮,文本内容为 1', done => {const wrapper = shallowMount(HelloWorld)const button = wrapper.findComponent('button')const countText = wrapper.findComponent('[data-testid="count-text"]')// 触发事件button.trigger('click')wrapper.vm.$nextTick(() => {expect(countText.text()).toBe('1') // 测试成功done()})// 或者,也可以使用全局方法 Vue.nextTick,它返回一个 Promise// 注意:使用 await 要给测试函数添加 async 关键字// 注意:在 Vue.nextTick 回调中执行断言有一些问题,稍后会介绍// await Vue.nextTick()
})

trigger 方法也会返回一个 Promise,我们可以 await trigger,然后再执行断言(注意测试函数要添加 async 关键字):

test('点击按钮,文本内容为 1', async () => {const wrapper = shallowMount(HelloWorld)const button = wrapper.findComponent('button')const countText = wrapper.findComponent('[data-testid="count-text"]')// 等待 Vue 完成 DOM 更新await button.trigger('click')expect(countText.text()).toBe('1') // 测试成功
})

如果不喜欢使用 async/await 也可以在 trigger().then() 回调中执行断言(不会像 nextTick 一样无法捕获,稍后会讲) :

test('点击按钮,count 为 1', () => {const wrapper = shallowMount(HelloWorld)const button = wrapper.findComponent('button')const countText = wrapper.findComponent('[data-testid="count-text"]')// 等待 Vue 完成 DOM 更新button.trigger('click').then(() => {expect(countText.text()).toBe('1')})
})

测试代码中使用 nextTick

当你在测试代码中使用 Vue.nextTick,并在回调中使用断言,断言抛出的错误可能不会被测试运行器捕获(尽管使用了 done),因为内部使用了 Promise:

test('错误不会被捕获,该测试将超时', done => {Vue.nextTick(() => {expect(true).toBe(false)done()})
})

关于这个问题,官方有两个建议:

test('建议1:修改 Vue 全局错误处理器,设置为 `done` 回调', done => {Vue.config.errorHandler = doneVue.nextTick(() => {expect(true).toBe(false)// 注意:这里仍要调用 done,否则断言成功后,测试会继续等待直到超时done()})
})test('建议2:在调用 `nextTick` 时不带参数,让其作为一个 Promise 返回', () => {return Vue.nextTick().then(() => {expect(true).toBe(false)})
})test('建议2:使用 async/await 写法', async () => {await Vue.nextTick()expect(true).toBe(false)
})

注意:这里讲的是 Vue 全局方法 Vue.nextTick,实例方法 vm.$nextTick() 可以在回调中执行断言,等待 done

断言触发的事件

每个挂载的包裹器都会通过其背后的 Vue 实例自动记录所有被触发的事件,可以使用 wrapper.emitted() 方法取回这些事件记录。

<!-- src\components\EventEmit.vue -->
<template><div><button data-testid="btn1" @click="$emit('foo')">按钮1</button><button data-testid="btn2" @click="$emit('foo', 123)">按钮2</button><button data-testid="btn3" @click="$emit('bar')">按钮3</button></div>
</template><script>
export default {}
</script>
import { shallowMount } from '@vue/test-utils'
import EventEmit from '@/components/EventEmit.vue'test('断言触发的事件', () => {const wrapper = shallowMount(EventEmit)const btn1 = wrapper.findComponent('[data-testid="btn1"]')const btn2 = wrapper.findComponent('[data-testid="btn2"]')const btn3 = wrapper.findComponent('[data-testid="btn3"]')// 通过点击按钮触发事件btn1.trigger('click')btn2.trigger('click')btn3.trigger('click')// 通过实例触发事件wrapper.vm.$emit('foo', 'from vm.$emit')// 获取事件记录console.log(wrapper.emitted())// 打印结果:// {//   foo: [ [], [ 123 ], [ 'from vm.$emit' ] ],//   bar: [ [] ]// }// 断言事件已经被触发expect(wrapper.emitted().foo).toBeTruthy()// 断言事件的数量expect(wrapper.emitted().foo.length).toBe(3)// 断言事件的有效数据expect(wrapper.emitted().foo[1]).toEqual([123])// 获取一个按触发先后排序的事件数组console.log(wrapper.emittedByOrder())// 注意:emittedByOrder 已废弃,将在未来版本移除,当前使用会抛出error提示(不影响测试结果)// 目前没有其他可获取顺序的替代API// 开发者认为断言事件的顺序是脆弱的不那么关键的测试。// ISSUE - https://github.com/vuejs/vue-test-utils/issues/1775// 打印结果:// [//   { name: 'foo', args: [] },//   { name: 'foo', args: [ 123 ] },//   { name: 'bar', args: [] },//   { name: 'foo', args: [ 'from vm.$emit' ] }// ]
})

浅渲染和深渲染

教程 - 浅渲染

Vue Test Utils 提供的 mountshallowMount 方法用来快速渲染挂载组件。

<!-- src\components\Foo.vue -->
<template><div><Bar /></div>
</template><script>
import Bar from './Bar'export default {components: { Bar }
}
</script>
<!-- src\components\Bar.vue -->
<template><div>Bar 组件</div>
</template>
// tests\unit\example.spec.js
import { shallowMount, mount } from '@vue/test-utils'
import Foo from '@/components/Foo.vue'test.only('Mount Test', () => {const shallowMountWrapper = shallowMount(Foo)const mountWrapper = mount(Foo)// shallowMountWrapper 是浅渲染,不会渲染子组件,使用 stub 标记占位(存根)console.log(shallowMountWrapper.html())// <div>//   <bar-stub></bar-stub>// </div>// mountWrapper 是深渲染,完全渲染所有子组件console.log(mountWrapper.html())// <div>//   <div>Bar 组件</div>// </div>
})

如果测试的组件不关心子组件,建议使用 shallowMount 降低资源消耗。

这篇关于Vue2 应用测试学习 01 - Vue Test Utils 介绍和快速体验的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1097914

相关文章

CSS3中使用flex和grid实现等高元素布局的示例代码

《CSS3中使用flex和grid实现等高元素布局的示例代码》:本文主要介绍了使用CSS3中的Flexbox和Grid布局实现等高元素布局的方法,通过简单的两列实现、每行放置3列以及全部代码的展示,展示了这两种布局方式的实现细节和效果,详细内容请阅读本文,希望能对你有所帮助... 过往的实现方法是使用浮动加

css渐变色背景|<gradient示例详解

《css渐变色背景|<gradient示例详解》CSS渐变是一种从一种颜色平滑过渡到另一种颜色的效果,可以作为元素的背景,它包括线性渐变、径向渐变和锥形渐变,本文介绍css渐变色背景|<gradien... 使用渐变色作为背景可以直接将渐China编程变色用作元素的背景,可以看做是一种特殊的背景图片。(是作为背

CSS自定义浏览器滚动条样式完整代码

《CSS自定义浏览器滚动条样式完整代码》:本文主要介绍了如何使用CSS自定义浏览器滚动条的样式,包括隐藏滚动条的角落、设置滚动条的基本样式、轨道样式和滑块样式,并提供了完整的CSS代码示例,通过这些技巧,你可以为你的网站添加个性化的滚动条样式,从而提升用户体验,详细内容请阅读本文,希望能对你有所帮助...

css实现图片旋转功能

《css实现图片旋转功能》:本文主要介绍了四种CSS变换效果:图片旋转90度、水平翻转、垂直翻转,并附带了相应的代码示例,详细内容请阅读本文,希望能对你有所帮助... 一 css实现图片旋转90度.icon{ -moz-transform:rotate(-90deg); -webkit-transfo

vue基于ElementUI动态设置表格高度的3种方法

《vue基于ElementUI动态设置表格高度的3种方法》ElementUI+vue动态设置表格高度的几种方法,抛砖引玉,还有其它方法动态设置表格高度,大家可以开动脑筋... 方法一、css + js的形式这个方法需要在表格外层设置一个div,原理是将表格的高度设置成外层div的高度,所以外层的div需要

5分钟获取deepseek api并搭建简易问答应用

《5分钟获取deepseekapi并搭建简易问答应用》本文主要介绍了5分钟获取deepseekapi并搭建简易问答应用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需... 目录1、获取api2、获取base_url和chat_model3、配置模型参数方法一:终端中临时将加

JavaScript中的isTrusted属性及其应用场景详解

《JavaScript中的isTrusted属性及其应用场景详解》在现代Web开发中,JavaScript是构建交互式应用的核心语言,随着前端技术的不断发展,开发者需要处理越来越多的复杂场景,例如事件... 目录引言一、问题背景二、isTrusted 属性的来源与作用1. isTrusted 的定义2. 为

四种Flutter子页面向父组件传递数据的方法介绍

《四种Flutter子页面向父组件传递数据的方法介绍》在Flutter中,如果父组件需要调用子组件的方法,可以通过常用的四种方式实现,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录方法 1:使用 GlobalKey 和 State 调用子组件方法方法 2:通过回调函数(Callb

Vue项目中Element UI组件未注册的问题原因及解决方法

《Vue项目中ElementUI组件未注册的问题原因及解决方法》在Vue项目中使用ElementUI组件库时,开发者可能会遇到一些常见问题,例如组件未正确注册导致的警告或错误,本文将详细探讨这些问题... 目录引言一、问题背景1.1 错误信息分析1.2 问题原因二、解决方法2.1 全局引入 Element

详解如何在React中执行条件渲染

《详解如何在React中执行条件渲染》在现代Web开发中,React作为一种流行的JavaScript库,为开发者提供了一种高效构建用户界面的方式,条件渲染是React中的一个关键概念,本文将深入探讨... 目录引言什么是条件渲染?基础示例使用逻辑与运算符(&&)使用条件语句列表中的条件渲染总结引言在现代