【架构师(第二十九篇)】Vue-Test-Utils 触发事件和异步请求
2023-06-13 09:15:45 时间
知识点
- 将
mock
对象断言为特定类型 使用jest.Mocked<T>
- 使用
it.only
来指定测试的case
- 使用
skip
跳过指定测试的case
测试内容
- 触发事件
trigger
方法
- 测试界面是否更新
- 特别注意
DOM
更新是个异步的过程 - 使用
async await
- 特别注意
- 更新表单
setValue
方法
- 验证事件是否发送
emitted
方法
- 测试异步请求
- 模拟第三方库实现
测试准备和结束
可以使用内置的一些钩子来简化一些通用的逻辑,以下钩子用于一次性完成测试准备。
- beforeAll
- afterAll
let wrapper: VueWrapper<any>;
describe('HelloWorld.vue', () => {
// 在多个 case 运行之前执行,只执行一次,由于这样会让所有的用例使用一个 `warpper` 实例,可能会造成错误。
beforeAll(() => {
// 获取组件
wrapper = shallowMount(HelloWorld, {
props: { msg },
});
});
// 在多个 case 运行之后执行,只执行一次
afterAll(() => {});
});
以下钩子用于每个测试用例测试准备。
- beforeEach
- afterEach
let wrapper: VueWrapper<any>;
describe('HelloWorld.vue', () => {
beforeEach(() => {
// 获取组件
wrapper = shallowMount(HelloWorld, {
props: { msg },
});
});
afterEach(() => {
mockAxios.get.mockReset();
});
});
测试建议
如果一个测试失败了,需要注意
- 它是否是唯一在运行的用例,使用
only
单独运行一次 - 如果单独运行没问题,整体运行出错,应该检查
beforeEach
,beforeAll
等全局钩子中的逻辑是否有问题,判断是否需要清空共享状态。
测试组件
父组件
<template>
<div>
<!-- 显示 props.msg 内容 -->
<h1>{{ msg }}</h1>
<!-- 按钮 点击 count ++ -->
<button class="add-count"
@click="addCount">{{ count }}</button>
<!-- 输入框 -->
<input type="text"
v-model="todo" />
<!-- 按钮 点击添加 todo -->
<button class="add-todo"
@click="addTodo">addTodo</button>
<!-- . todos 列表 渲染 todo -->
<ul>
<li v-for="(todo, index) in todos"
:key="index">{{ todo }}</li>
</ul>
<!-- 按钮 点击发起异步请求 -->
<button class="load-user"
@click="loadUser">loadUser</button>
<!-- 加载动画 -->
<p v-if="user.loading"
class="loading">loading</p>
<!-- 显示数据 -->
<div v-else
class="user-name">{{ user?.data?.username }}</div>
<!-- 错误提示 -->
<p v-if="user.error"
class="error">error</p>
<!-- 子组件 传递 msg = 1234 -->
<hello-com msg="1234"></hello-com>
</div>
</template>
<script setup lang="ts">
import axios from 'axios'
import { defineProps, ref, defineEmits, reactive } from 'vue'
import HelloCom from './hello.vue'
// 定义props
defineProps({
msg: String
})
// 定义事件
const emit = defineEmits(['send'])
// 初始化 count
const count = ref(0)
// count++
const addCount = () => {
count.value++
}
// 初始化 input 内容
const todo = ref('')
// 初始化 todos 列表
const todos = ref<string[]>([])
// 添加 todo 到 todos 列表
const addTodo = () => {
if (todo.value) {
todos.value.push(todo.value)
emit('send', todo.value)
}
}
// 初始化异步请求数据
const user = reactive({
data: null as any,
loading: false,
error: false
})
// 异步请求
const loadUser = () => {
user.loading = true
axios.get("https://jsonplaceholder.typicode.com/users/1").then((resp) => {
console.log(resp)
user.data = resp.data
}).catch(() => {
user.error = true
}).finally(() => {
user.loading = false
})
}
</script>
子组件
<template>
<h1>{{ msg }}</h1>
</template>
<script setup lang="ts">
import { defineProps } from 'vue'
const props = defineProps({
msg: String
})
</script>
测试代码
Dom
更新为异步操作,需要使用 async await
。
import axios from 'axios';
import flushPromises from 'flush-promises';
import type { VueWrapper } from '@vue/test-utils';
import { shallowMount } from '@vue/test-utils';
import HelloWorld from '@/components/HelloWorld.vue';
jest.mock('axios');
//将 mock 对象断言为特定类型 使用 jest.Mocked<T>
const mockAxios = axios as jest.Mocked<typeof axios>;
const msg = 'new message';
let wrapper: VueWrapper<any>;
describe('HelloWorld.vue', () => {
beforeEach(() => {
// 获取组件
wrapper = shallowMount(HelloWorld, {
props: { msg },
});
});
afterEach(() => {
mockAxios.get.mockReset();
});
// 测试点击 button, count 增加
it('should update the count when clicking the button', async () => {
// 触发点击事件
await wrapper.get('.add-count').trigger('click');
// 数字变为 1 (初始为0)
expect(wrapper.get('.add-count').text()).toBe('1');
});
// 测试 更新表单 点击 add button
it('should add todo when fill the input and click the add button', async () => {
const todoContent = 'test todo';
// 触发 input 事件 , 设置值为 todoContent
await wrapper.get('input').setValue(todoContent);
// 断言 input 的值为 todoContent
expect(wrapper.get('input').element.value).toBe(todoContent);
// 触发 button 点击事件
await wrapper.get('.add-todo').trigger('click');
// 断言 有一个 li
expect(wrapper.findAll('li')).toHaveLength(1);
// 断言 li 的内容是 todoContent
expect(wrapper.get('li').text()).toBe(todoContent);
// 断言 触发了 名为 send 的 emit 事件
expect(wrapper.emitted()).toHaveProperty('send');
// 获取 send 事件的 对象
const events = wrapper.emitted('send')!;
// 检查对象内容是否相同使用 toEqual, toBe 要求引用也相同
expect(events[0]).toEqual([todoContent]);
});
// 使用 it.only 来指定测试的 case
it('should load user message when click the load button', async () => {
// mock service
mockAxios.get.mockResolvedValueOnce({
data: {
username: 'warbler',
},
});
// 触发点击事件
await wrapper.get('.load-user').trigger('click');
// 断言 请求是否被调用
expect(mockAxios.get).toHaveBeenCalled();
// 断言 loading 是否出现
expect(wrapper.find('.loading').exists()).toBeTruthy();
// 让 promise 完成,并且界面更新完成
await flushPromises();
// 断言 loading 消失
expect(wrapper.find('.loading').exists()).toBeFalsy();
// 断言 username 显示
expect(wrapper.get('.user-name').text()).toBe('warbler');
});
});
测试结果
相关文章
- 手把手带你使用webpack4构建一个Vue开发编译环境,并实现代码分割,css代码分离
- vue中特殊的prop和事件
- vue判断map是否为空
- 手写Vue数据绑定
- Vue笔记(11) vue-router
- vue md5.js_VUE.js
- Vue响应式依赖收集原理分析-vue高级必备
- 极简开发VFP混搭VUE,一起迈入新世界的大门
- 记录第二天-Vue起步
- layui表单提交数据_vue表单值无变化不让提交
- VUE组件封装_vue组件内部双向绑定
- vue 调用子组件方法失败_Vue子组件调用父组件的方法及常见问题「建议收藏」
- Vue子组件调用父组件的方法「建议收藏」
- vuetify富文本编辑器_vue富文本编辑器的使用
- Vue使用Axios携带token请求后端接口
- vue.js客服系统实时聊天项目开发(十一)处理发送消息enter事件以及实现ctrl+enter换行
- Vue Router前端路由
- vue移动端h5页面根据屏幕适配的四种方案
- IDEA安装Vue插件后为什么创建.vue文件不显示vue图标
- Vue使用printjs组件打印页面
- 使用Vue技术从MSSQL中获取数据(vue获取mssql数据)
- Vue结合Redis,加快数据读取速度(vue读取redis)
- 基于Vue的Redis网页设计(vue设计redis页面)
- Vue实现Redis订阅消息的实现方案(vue 订阅redis)
- Vue操作Redis掌握前端数据管理利器(vue操作redis)
- Vue如何利用Redis加速应用程序性能(vue如何使用redis)
- Vue搭配Redis做针对性取值(vue redis取值)