知识点
- 将
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
等全局钩子中的逻辑是否有问题,判断是否需要清空共享状态。
测试组件
父组件
代码语言: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
。
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');
});
});