首页
学习
活动
专区
圈层
工具
发布
50 篇文章
1
【架构师(第一篇)】整体需求分析和架构设计
2
【架构师(第二篇)】脚手架架构设计和框架搭建
3
【架构师(第三篇)】脚手架开发之掌握Lerna操作流程
4
【架构师(第四篇)】脚手架开发之Lerna源码分析
5
【架构师(第五篇)】脚手架之import-local执行流程及简历设计
6
【架构师(第六篇)】脚手架之需求分析和架构设计
7
【架构师(第七篇)】脚手架之准备阶段编写
8
【架构师(第八篇)】脚手架之 commander 框架使用方法
9
【架构师(第九篇)】如何让 Node 环境支持 ES Module
10
【架构师(第十篇)】脚手架之注册命令及架构优化
11
【架构师(第十一篇)】脚手架之命令注册和执行过程开发
12
【架构师(第十二篇)】脚手架之命令行交互工具 inquirer.js 使用方法
13
【架构师(第十三篇)】脚手架之创建项目准备阶段开发
14
【架构师(第十四篇)】脚手架之 egg.js 和 mongodb 的使用
15
【架构师(第十五篇)】脚手架之创建项目模板开发
16
【架构师(第十六篇)】脚手架之创建项目模板的下载与更新
17
【架构师(第十七篇)】脚手架之 ejs 和 glob 的使用
18
【架构师(第十八篇)】脚手架之项目模板的安装
19
【架构师(第十九篇)】脚手架之组件库模板开发
20
【架构师(第二十篇)】脚手架之自定义模板及第一阶段总结
21
【架构师(第二十一篇)】编辑器开发之需求分析和架构设计
22
【架构师(第二十二篇)】编辑器开发之项目整体搭建
23
【架构师(第二十三篇)】编辑器开发之画布区域组件的渲染
24
【架构师(第二十四篇)】编辑器开发之添加模版到画布
25
【架构师(第二十五篇)】编辑器开发之属性编辑区域表单渲染
26
【架构师(第二十六篇)】编辑器开发之属性编辑同步渲染
27
【架构师(第二十七篇)】前端单元测试框架 Jest 基础知识入门
28
【架构师(第二十八篇)】 测试工具 Vue-Test-Utils 基础语法
29
【架构师(第二十九篇)】Vue-Test-Utils 触发事件和异步请求
30
【架构师(第三十篇)】Vue-Test-Utils 全局组件和第三方库 vuex | vue-router
31
【架构师(第三十一篇)】前端测试之 TDD 的开发方式
32
【架构师(第三十二篇)】 通用上传组件开发及测试用例
33
【架构师(第三十三篇)】 Vue 中的实例及本地图片预览
34
【架构师(第三十四篇)】 业务组件库开发之 vue3 的插件系统
35
【架构师(第三十五篇)】 业务组件库开发之使用 Rollup 进行打包
36
【架构师(第三十六篇)】 业务组件库开发之发布到 NPM
37
【架构师(第三十七篇)】 服务端开发之后端框架与数据库技术选型
38
【架构师(第三十八篇)】 服务端开发之本地安装最新版 MySQL 数据库
39
【架构师(第三十九篇)】 服务端开发之连接 MySQL 数据库
40
【架构师(第四十篇)】 服务端开发之连接 Mongodb 数据库
41
【架构师(第四十一篇)】 服务端开发之安装并连接 Redis数据库
42
【架构师(第四十二篇)】 服务端开发之常用的登录鉴权方式
43
【架构师(第四十三篇)】 服务端开发之单元测试和接口测试
44
【架构师(第四十四篇)】 服务端开发之 pm2 和 nginx 介绍
45
【架构师(第四十五篇)】 服务端开发之认识 Github actions
46
【架构师(第四十六篇)】 服务端开发之安装 Docker
47
【架构师(第四十七篇)】 服务端开发之认识 Docker
48
【架构师(第四十八篇)】 服务端开发之 Dockerfile
49
【架构师(第四十九篇)】 服务端开发之认识 Docker-compose
50
【架构师(第五十篇)】 服务端开发之自动发布到测试机
清单首页架构文章详情

【架构师(第二十九篇)】Vue-Test-Utils 触发事件和异步请求


知识点

  • mock 对象断言为特定类型 使用 jest.Mocked<T>
  • 使用 it.only 来指定测试的 case
  • 使用 skip 跳过指定测试的 case

测试内容

  • 触发事件
    • trigger 方法
  • 测试界面是否更新
    • 特别注意 DOM 更新是个异步的过程
    • 使用 async await
  • 更新表单
    • setValue 方法
  • 验证事件是否发送
    • emitted 方法
  • 测试异步请求
    • 模拟第三方库实现

测试准备和结束

可以使用内置的一些钩子来简化一些通用的逻辑,以下钩子用于一次性完成测试准备。

  • beforeAll
  • afterAll
代码语言:javascript
复制
let wrapper: VueWrapper<any>;
describe('HelloWorld.vue', () => {
  // 在多个 case 运行之前执行,只执行一次,由于这样会让所有的用例使用一个 `warpper` 实例,可能会造成错误。
  beforeAll(() => {
    // 获取组件
    wrapper = shallowMount(HelloWorld, {
      props: { msg },
    });
  });
  // 在多个 case 运行之后执行,只执行一次
  afterAll(() => {});
});

以下钩子用于每个测试用例测试准备。

  • beforeEach
  • afterEach
代码语言:javascript
复制
let wrapper: VueWrapper<any>;
describe('HelloWorld.vue', () => {
  beforeEach(() => {
    // 获取组件
    wrapper = shallowMount(HelloWorld, {
      props: { msg },
    });
  });
  afterEach(() => {
    mockAxios.get.mockReset();
  });
});

测试建议

如果一个测试失败了,需要注意

  • 它是否是唯一在运行的用例,使用 only 单独运行一次
  • 如果单独运行没问题,整体运行出错,应该检查 beforeEachbeforeAll 等全局钩子中的逻辑是否有问题,判断是否需要清空共享状态。

测试组件

父组件

代码语言:javascript
复制
<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>

子组件

代码语言:javascript
复制
<template>
  <h1>{{ msg }}</h1>
</template>

<script setup lang="ts">
import { defineProps } from 'vue'
const props = defineProps({
  msg: String
})
</script>

测试代码

Dom 更新为异步操作,需要使用 async await

代码语言:javascript
复制
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');
  });
});

测试结果

下一篇
举报
领券