前言:我在工作中利用fantastic-admin开发的项目进行测试,发现vitest很多包不兼容,并且vitest很多文档和文章对接口测试这个模块写的并不完善,所以我根据我目前的项目总结了一部分使用方法,报错的那部分有可能你的项目也使用了该安装包,可能也会报错,也可以用我的解决方式去解决,在文章最后附上了真实接口的测试案例,可以参考使用
使用的测试框架vitest+Test Utils for Vue.js +axios-mock-adapter三个主要的包进行测试
项目配置
packge.json配置
vitest+Test Utils for Vue.js 3需要的包
packge.json配置,vitest:ui": "vitest --ui"这个为启动浏览器查看测试覆盖率",test:unit这个为运行终端测试
"scripts": {
"preview": "vite preview",
"test:unit": "vitest",
"preview:test": "vite preview --outDir test",
"vitest:ui": "vitest --ui",
"vitest:debug": "vitest ./src/components/10/mock.vuex.test.ts --inspect-brk --pool forks --poolOptions.forks.singleFork ",
},
"devDependencies": {
"@pinia/testing": "^0.1.3",
"@rushstack/eslint-patch": "^1.3.3",
"@tsconfig/node20": "^20.1.2",
"@types/jsdom": "^21.1.6",
"@types/qrcode": "^1.5.5",
"@vitest/coverage-v8": "^1.2.2",
"@vitest/ui": "^1.2.2",
"@vue/eslint-config-prettier": "^8.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"@vue/test-utils": "^2.4.4",
"@vue/tsconfig": "^0.5.1",
"@wangeditor/editor-for-vue": "^5.1.12",
"eslint-plugin-vitest-globals": "^1.4.0",
"eslint-plugin-vue": "^9.17.0",
"jsdom": "^24.0.0",
"prettier": "^3.0.3",
"vite-plugin-mock": "^3.0.2",
"vitest": "^1.2.2"
}
vitest.config.ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import ElementPlus from 'element-plus'
import type { UserConfig as VitestUserConfigInterface } from "vitest/config"
import AutoImport from 'unplugin-auto-import/vite'
import createI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
import path from 'path-browserify'
const vitestConfig: VitestUserConfigInterface = {
test: {
globals: true,
environment: "jsdom",
setupFiles: './src/setupTests.js',
},
}
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
vueJsx(),
ElementPlus,
createI18nPlugin({
include: [path.resolve(__dirname, './locales/**')]
}),
AutoImport({
imports: ['vue', 'vue-router', 'pinia'], // 自动导入 vue、vue-router 和 pinia 的 API
dts: './src/types/auto-imports.d.ts', // 生成 TypeScript 类型声明文件
dirs: ['./src/utils/composables/**'], // 自动导入指定目录下的模块
}),
],
resolve: {
alias: {
'virtual:generated-pages': fileURLToPath(new URL('./src/__mocks__/virtual-generated-pages.ts', import.meta.url)),
'virtual:meta-layouts': fileURLToPath(new URL('./src/__mocks__/virtual-meta-layouts.ts', import.meta.url)),
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
test: vitestConfig.test,
})
vite.config.ts
import dayjs from 'dayjs'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { defineConfig, loadEnv } from 'vite'
import pkg from './package.json'
import createVitePlugins from './vite/plugins'
// https://vitejs.dev/config/
export default async ({ mode, command }) => {
const env = loadEnv(mode, process.cwd())
// 全局 scss 资源
const scssResources = []
fs.readdirSync('src/assets/styles/resources').forEach((dirname) => {
if (fs.statSync(`src/assets/styles/resources/${dirname}`).isFile()) {
scssResources.push(`@use "src/assets/styles/resources/${dirname}" as *;`)
}
})
return defineConfig({
// 开发服务器选项 https://cn.vitejs.dev/config/#server-options
server: {
open: true,
port: 9000,
proxy: {
'/proxy': {
// target: env.VITE_APP_API_BASEURL,
target: 'http://192.168.101.112:8080',
changeOrigin: command === 'serve' && env.VITE_OPEN_PROXY === 'true',
rewrite: path => path.replace(/\/proxy/, ''),
},
},
},
// 构建选项 https://cn.vitejs.dev/config/#server-fsserve-root
build: {
outDir: mode === 'production' ? 'dist' : `dist-${mode}`,
sourcemap: env.VITE_BUILD_SOURCEMAP === 'true',
rollupOptions: {
external: ['webpack'], // 这里配置 external
},
},
define: {
__SYSTEM_INFO__: JSON.stringify({
pkg: {
version: pkg.version,
dependencies: pkg.dependencies,
devDependencies: pkg.devDependencies,
},
lastBuildTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
}),
},
plugins: createVitePlugins(env, command === 'build'),
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'#': path.resolve(__dirname, 'src/types'),
},
},
css: {
preprocessorOptions: {
scss: {
additionalData: scssResources.join(''),
},
},
},
})
}
tsconfig.node.json
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"skipLibCheck": true
},
"include": [
"package.json",
"vite.config.ts",
"vite/**/*.ts"
]
}
安装依赖后和vitest+fantastic-admin配置后的报错
报错提示:ref is not a function
项目使用了这个包unplugin-auto-import/vite,他的作用是:全局配置一些导入,在auto-imports.d文件夹配置,如这里的ref配置后就全局应用了,不用每个文件里使用时都去import导入,但是这边vitest测试环境下,这里还是无法找到ref。
解决方式:添加auto-imports.d文件
export {}
declare global {
const afterAll: typeof import('vitest')['afterAll']
const afterEach: typeof import('vitest')['afterEach']
const assert: typeof import('vitest')['assert']
const beforeAll: typeof import('vitest')['beforeAll']
const beforeEach: typeof import('vitest')['beforeEach']
const chai: typeof import('vitest')['chai']
const describe: typeof import('vitest')['describe']
const expect: typeof import('vitest')['expect']
const it: typeof import('vitest')['it']
const suite: typeof import('vitest')['suite']
const test: typeof import('vitest')['test']
const vi: typeof import('vitest')['vi']
const vitest: typeof import('vitest')['vitest']
}
在vitest.config.ts添加配置
import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig({
plugins: [
vue(),
vueJsx(),
AutoImport({
imports: ['vue', 'vue-router', 'pinia'], // 自动导入 vue、vue-router 和 pinia 的 API
dts: './src/types/auto-imports.d.ts', // 生成 TypeScript 类型声明文件
dirs: ['./src/utils/composables/**'], // 自动导入指定目录下的模块
})
],
报错提示:document is not a function
原因vitest没有引入文档对象模型,在测试的时候也会测试页面点击事件
解决方式:在vitest.config.ts添加配置
import type { UserConfig as VitestUserConfigInterface } from "vitest/config"
const vitestConfig: VitestUserConfigInterface = {
test: {
globals: true,
environment: "jsdom", //这里配置jsdom终端就会提示安装jsadom了
},
}
报错提示:virtual-generated-pages is not a function
报错提示:virtual-meta-layouts is not a function
这两个报错,解决方法是在src新建_mocks_文件夹,新建virtual-generated-pages.ts和virtual-meta-layouts.ts
文件内容分别为:
//virtual-generated-pages.ts
import { RouteRecordRaw } from 'vue-router';
const routes: RouteRecordRaw[] = []; // 根据实际需求定义路由
export default routes;
//virtual-meta-layouts.ts
export const setupLayouts = (routes: any[]) => routes;
export const createGetRoutes = (router: any, withLayout = false) => () => [];
//vitest.config.ts配置
resolve: {
alias: {
'virtual:generated-pages': fileURLToPath(new URL('./src/__mocks__/virtual-generated-pages.ts', import.meta.url)),
'virtual:meta-layouts': fileURLToPath(new URL('./src/__mocks__/virtual-meta-layouts.ts', import.meta.url)),
}
},
报错提示:No known conditions for "./messages" specifier in "@intlify/unplugin-vue-i18n" package
这个错误通常出现在使用 @intlify/unplugin-vue-i18n
插件时,表示在你的项目中找不到对应的配置或条件。
在vitest.config.ts配置
import createI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
export default defineConfig({
plugins: [
vue(),
vueJsx(),
ElementPlus,
createI18nPlugin({ //这里配置
include: [path.resolve(__dirname, './locales/**')]
}),
AutoImport({
imports: ['vue', 'vue-router', 'pinia'], // 自动导入 vue、vue-router 和 pinia 的 API
dts: './src/types/auto-imports.d.ts', // 生成 TypeScript 类型声明文件
dirs: ['./src/utils/composables/**'], // 自动导入指定目录下的模块
}),
],
报错提示:window.matchMedia is not a function
window.matchMedia
是一个用于监听媒体查询变化的 JavaScript API,它通常在浏览器环境中可用,如果在测试环境下
则需要配置
新建src/setupTests.js文件
global.window = Object.create(window);
global.window.matchMedia = function(query) {
return {
matches: query === '(prefers-color-scheme: dark)',
media: query,
onchange: null,
addListener: () => {}, // Deprecated
removeListener: () => {}, // Deprecated
addEventListener: () => {},
removeEventListener: () => {},
dispatchEvent: () => true,
};
};
在vitest.config.js配置
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
setupFiles: './src/setupTests.js', // 指向你的设置文件
},
});
ReferenceError: requestAnimationFrame is not defined
requestAnimationFrame
是一个浏览器提供的 API,用于在下次重绘之前调用指定的回调函数更新动画,它不是在 Node.js 环境中定义的。错误信息表明,你的测试环境试图在 Node.js 中调用 requestAnimationFrame
,但由于它仅在浏览器环境中可用,因此抛出了 ReferenceError
。
以下是一个简单的 requestAnimationFrame
模拟实现,你可以在测试环境的设置文件中添加它:
// setupTests.js
global.requestAnimationFrame = (callback) => {
setTimeout(callback, 0);
};
global.cancelAnimationFrame = (id) => {
clearTimeout(id);
};
以下为测试框架用法
项目测试框架示例
采用vitest语法Test Utils for Vue.js 3包的一些方法如mount拿到组件实例,axios-mock-adapter跑axios接口
钩子函数
beforeAll
注册一个回调函数,在开始运行当前上下文中的所有测试之前调用一次。 如果函数返回一个 Promise ,Vitest 会等待承诺解析后再运行测试。beforeEach
注册一个回调函数,在当前上下文中的每个测试运行前调用。 如果函数返回一个 Promise ,Vitest 会等待承诺解析后再运行测试。afterEach
注册一个回调函数,在当前上下文中的每个测试完成后调用。 如果函数返回一个承诺,Vitest 会等待承诺解析后再继续。aftereAll
注册一个回调函数,以便在当前上下文中所有测试运行完毕后调用一次。 如果函数- 返回一个 Promise ,Vitest 会等待承诺解析后再继续。
describe('测试 component', () => {
let component:MyComponent
// 在每个测试之前执行的准备工作
beforeEach(() => {
// 创建一个新的组件实例
component = new MyComponent()
})
test('fetchData 方法应正确获取数据', async () => {
await component.fetchData()
// 断言数据是否符合预期
expect(component.data).toEqual([1, 2, 3, 4])
})
test('data 数组应为空数组', () => {
// 断言数据是否为空数组
expect(component.data).toEqual([])
})
})
断言常用方法
it('断言 ', () => {
// expect这个是断言
expect(1).toBe(1) //toBe判断两数是否相等
expect(1).not.toBe(2)//not.toBe判断两数是否不相等
expect(3).toBeGreaterThan(2)//toBeGreaterThan判断是否比断言的数小
expect(3).toBeLessThan(4)//toBeLessThan判断是否比断言的数大
expect(3).toBeGreaterThanOrEqual(3) //小于等于
expect(3).toBeGreaterThanOrEqual(2)
expect(3).toBeLessThanOrEqual(3)//大于等于
expect(3).toBeLessThanOrEqual(4)
expect(0.2 + 0.1).toBeCloseTo(0.3, 5)//检测浮点数,第二个是小数点后几位
expect.soft(1 + 1).toBe(3) //这里测试失败也会执行下面的代码
expect(getApples()).toBeDefined() //判断这个函数是否有返回值
expect(getApplesFromStock('Mary')).toBeUndefined()//判断这个函数是否没有返回值
expect({a:1}).toEqual({a:1}) //判断值是否相等,这个判断对象和数组类型
expect([{a:1},{b:2}]).toEqual([{a:1},{b:2}])
expect([undefined,1]).toStrictEqual([undefined, 1])
expect([1]).not.toStrictEqual([undefined, 1])//能够检测里面的undefined
expect(undefined).not.toBeDefined() //判断值不等于undefined
expect(undefined).toBeUndefined()//判断值等于undefined
expect(true).toBeTruthy()//判断expect里面的值是否等于true
expect(false).toBeFalsy()//判断expect里面的值是否等于false
expect(null).toBeNull()//判断是否为null
expect(['apple', 'orange']).toContain('orange')//能够断言这个字符串能否在数组中
expect('123abc123').toContain('123abc')//也能断言这个字符串是否被包含类似include方法
//toMatchObject判断这个对象是否包含另一个对象的部分属性
expect([{ foo: 'bar' }, { baz: 1 }]).toMatchObject([{ foo: 'bar' }, { baz: 1 }])
expect({ obj: { name: 'xxx' }, height: 10 }).toMatchObject({ height: 10 })
expect({ obj: { name: 'xxx' }, height: 10 }).not.toMatchObject({ name: 'xxx' })
// 函数断言
// toHaveBeenCalled 判断函数是否被调用
// toHaveBeenCalledTimes 判断函数被调用的次数
// toHaveBeenCalledWith 判断函数被调用的时候传递了什么参数
const market = {
buy(subject: string, amount: number) {
// ...
}
}
const buySpy = vi.spyOn(market, 'buy')
expect(buySpy).not.toHaveBeenCalled() //这里判断函数是否没有被调用
market.buy('apples', 10)
market.buy('apples', 10)//这里调用两次函数
expect(buySpy).toHaveBeenCalled() //判断函数是否被调用
expect(buySpy).toHaveBeenCalledTimes(2)//是否被调用两次
expect(buySpy).toHaveBeenCalledWith('apples', 10)//被调用传的参数
})
组件测试
mount 和 shallowMount
在 Vue Test Utils 中,mount
和 shallowMount
是两个常用的方法,用于创建 Vue 组件的包裹器(Wrapper)对象,并提供对组件的访问和操作。这两个方法之间的区别在于其处理子组件的方式。
mount
方法:
-
mount
方法会创建一个完整的组件包裹器,包括渲染组件及其子组件。- 它适用于需要进行组件完整渲染并测试组件及其子组件之间交互的情况。
mount
方法会触发组件的生命周期钩子函数,如created
、mounted
等,也会执行子组件的逻辑。- 由于会渲染所有子组件,
mount
方法的性能相对较低,特别是在测试大型组件树时。
shallowMount
方法:
-
shallowMount
方法会创建一个浅层的组件包裹器,它只会渲染当前组件,而不会渲染其子组件。- 它适用于只关注当前组件的行为,并希望忽略子组件逻辑的情况。
shallowMount
方法不会触发子组件的生命周期钩子函数,仅执行当前组件的逻辑。- 由于只渲染当前组件,
shallowMount
方法的性能相对较高,特别是在测试大型组件树时。
组件测试示例
组件测试就是通过获取组件元素的标签来测试组件里面的元素是否正确
import Button from './Button.vue'
defineProps<{
msg: string
}>()
</script>
<template>
<div>
<h1>{{ msg }}</h1>
<h1>{{ msg }}</h1>
<h3 data-testid="title">title</h3>
<Button></Button>
<div v-if="false" data-testid="if">if button</div>
<div v-show="false" data-testid="show">show button</div>
</div>
</template>
it('查询元素,可以同过标签、属性、id、类名查找', () => {
const wrapper = mount(Find, { props: { msg: 'Hello Vitest' } })
const title = wrapper.find('[data-testid="title"]') // 属性查找
expect(title.text()).toBe('title')
// 获取第一个h1元素
expect(wrapper.get('h1').text()).toBe('Hello Vitest')
// 获取第一个 h1 元素
expect(wrapper.findAll('h1').at(0)?.text()).toBe('Hello Vitest')
// 获取第二个 h1 元素
expect(wrapper.findAll('h1').at(1)?.text()).toBe('Hello Vitest')
expect(wrapper.find('h2')) // 找不到返回 undefined
// expect(wrapper.get('h2')) // 找不到会直接报错
// console.log(wrapper.findAll('h2'))
//查找button
//findComponent 查找元素,找不到返回 undefined
//getComponent 查找元素,找不到会报错
//findAll 查找多个元素,找不到会返回空数组
expect(wrapper.getComponent(Button).text()).toBe('default buttonif buttonshow button')
expect(wrapper.findComponent(Button).text()).toBe('default buttonif buttonshow button')
expect(wrapper.findAllComponents(Button).at(0)?.text()).toBe('default buttonif buttonshow button')
})
it('元素是否展示,是否可见 ', () => {
const wrapper = mount(Button)
const showDom = wrapper.find('[data-testid="show"]')
const ifDom = wrapper.find('[data-testid="if"]')
expect(showDom.isVisible()).toBeFalsy()
expect(showDom.exists()).not.toBeFalsy()
expect(ifDom.exists()).toBeFalsy()
})
//wrapper.html()输出 dom 结构
//wrapper.text()输出文本
it('mount', () => {
const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
expect(wrapper.text()).toContain('Hello Vitest')
expect(wrapper.html()).toContain('Hello Vitest')
console.log('text ===',wrapper.text()) // Hello Vitest
console.log('html ===',wrapper.html()) // <div> <h1>Hello Vitest</h1></div>
})
这里判断if,在实际代码里可以测试点击事件之后,然后判断受到该按钮影响的元素是否为true
事件测试
普通事件:
在 vue 测试中,核心的 api 是 wrapper 提供的 trigger 方法来触发。 例如,我们来一个最简单的单击 click 事件,点击之后,展示字符 vitest
import { ref } from 'vue'
const show = ref(false)
const onClick = () => {
show.value = true
}
</script>
<template>
<div>
<div v-if="show">vitest</div>
<div @click="onClick" data-testid="button">按钮</div>
</div>
</template>
import { mount } from '@vue/test-utils'
import Button from './Button.vue'
describe('click button', () => {
it('mount', async () => {
const wrapper = mount(Button)
const button = wrapper.find('[data-testid="button"]') //通过标签查询按钮
expect(wrapper.text()).not.toContain('vitest')
await button.trigger('click') //是否为点击事件
expect(wrapper.text()).toContain('vitest') //按钮的内容
// wrapper.vm拿到组件的实例
expect( wrapper.vm.show).toBe(true)
//await button.trigger('dbclick')
//await button.trigger('click.left')
//await button.trigger('keydown.alt.enter')
})
})
表单事件:
表单事件就是 checked 事件,input 事件,select 事件,submit 事件。
<script setup lang="ts">
import { ref } from 'vue'
const name = ref('')
const isMan = ref(1)
const country = ref(['jpy'])
</script>
<template>
<div>
<input v-model="name" data-testid="input"/>
<div>{{ name }}</div>
<h2>radio</h2>
<input type="radio" v-model="isMan" data-testid="radio-1" value="1"/>
<input type="radio" v-model="isMan" data-testid="radio-2" value="2"/>
<div>{{ isMan }}</div>
<h2>select</h2>
<select name="country" data-testid="select-country" v-model="country" >
<option value="cn" label="cn"></option>
<option value="jpy" label="jpy"></option>
<option value="uk" label="uk"></option>
</select>
<div>
{{ country }}
</div>
</div>
</template>
通过 setValue 给表单赋值,然后断言填充的值是否正确
it('input', async () => {
const wrapper = mount(Form)
const input = wrapper.find('[data-testid="input"]')
await input.setValue('123');
expect(wrapper.text()).toContain('123')
})
it('radio', async () => {
const wrapper = mount(Form)
expect(wrapper.text()).toContain('1')
const input = wrapper.find('[data-testid="radio-2"]')
await input.setValue(true)
expect(wrapper.text()).toContain('2')
})
it('select', async () => {
const wrapper = mount(Form)
const select = wrapper.find('[data-testid="select-country"]')
expect(wrapper.text()).toContain('jpy')
await select.setValue(['uk'])
expect(wrapper.text()).toContain('uk')
})
mock函数
通过mock函数来测试函数的返回值或者请求等等。
vi.fn
定义一个假的requestFn
函数,这个requestFn
可以记录是否被调用,调用参数是什么,也可以设置任何假的返回值。我们不需要关注他是不是真的发送请求给后端。
测试接口调用
import { mount } from '@vue/test-utils'
import MessageManager from './messageManager.vue'
import { describe, it, expect } from 'vitest';
describe('click button1', () => {
const wrapper = mount(MessageManager)
it('delPublich', async () => {
// 点击新增公告
//这里相当于模拟了页面的点击按钮弹出弹框,也可以在里面加上弹框的显示属性是否变为true,这里暂时没加
const deleteButton = wrapper.find('[data-testid="button"]')
await deleteButton.trigger('click')
// 确保弹框组件完全渲染
await wrapper.vm.$nextTick()
console.log(wrapper.html()) // 打印组件的 HTML 内容,检查弹出框是否存在
//这里是测试弹窗弹出来之后再次点击弹框里面的新增公告按钮,并且提交请求
// 找到确认按钮并触发提交表单操作
const button = wrapper.find('[data-testid="submitForm"]')
await button.trigger('click')
let req={
m_type: 1,
title: '',
content: '',
status: 1
}
const requestFn = vi.fn((req) => {
return {
data: {
name: 'xxx'
}
}
})
// 测试代码中调用模拟函数
requestFn(req)
// 断言模拟函数的调用信息
expect(requestFn).toHaveBeenCalled() //判断是否被调用
expect(requestFn).toHaveBeenCalledWith(req) // 被调用的时候参数是 req })
expect(requestFn(req)).toEqual({ //判断调用后的返回值
data: {
name: 'xxx'
}
})
// console.log(button.html()) // 打印按钮的 HTML 内容,检查是否能找到按钮
// expect(button.exists()).toBe(true) // 确认按钮存在
// await button.trigger('click')
})
})
mock测试接口
先看需求
这是获取页面数据和确认按钮的请求函数
这是具体的请求
第一步新建文件夹MockAdapter.js
import MockAdapter from 'axios-mock-adapter'
import api from '@/api/index' //二次封装的axios
if(import.meta.env.VITE_RUN_MOCK){ //这里是全局变量要设置个,测试环境这个变量设置为true
//不然new MockAdapter(api)出现在dev环境下真实的接口不会请求,因为mock会截断真实请求
const mock = new MockAdapter(api)
const userEdit={
code:0,
data:{
mid: "47b3c773-efca-4518-a259-14c1251332aa", //代理mid
name: "邢捕头", //代理商名称
phone: "18482235177", //代理商手机号
status:2
}
}
//这样发起请求/api/user/info这个地址的时候就会匹配对应的mock请求
mock.onGet('/api/user/info').reply(200) //这是第一个接口
mock.onGet('/api/agent/child/list').reply(200) //这是第二个接口
//模拟 POST 请求
mock.onPost('/api/user/edit').reply(200,userEdit);
}
然后写好测试用列:
之所以用MockAdapter测试,原因在于用这个mock出来的数据下一个it测试也可以使用,如这里获取到mock的数据后,可以拿到这个数据进行下一步测试,而在上面的mock函数测试,利用vi.fn进行测试是无法复用数据的,只是一次性的数据。
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest';
import { vi } from 'vitest';
import api from '@/api/index'
import agent_information from '@/views/basic_information/agent_information.vue'
describe('个人信息界面', () => {
const wrapper = mount(agent_information)
let spy;
let spyPost
beforeEach(() => {
spy = vi.spyOn(api, 'get'); //这里vi.spyOn可以监听到上图的mock
spyPost = vi.spyOn(api, 'post');
});
afterEach(() => {
spy.mockRestore();
spyPost.mockRestore();
});
it('测试下级代理商接口', async () => {
// 模拟第一个接口的返回值
const mockUserInfoResponse = {
data: {
name: 'John Doe',
phone: '1234567890',
uuid: 'abc-123',
},
};
spy.mockResolvedValue(mockUserInfoResponse); //这里是mock返回成功的值
console.log(spy,'spy') //这里打印的方法我会用图单独标出来
// 调用组件上 getData方法请求,然后上图的mock就会进行截断,并且返回mockUserInfoResponse这个数据
await wrapper.vm.getData();
//这里就表示第一个接口mock测试成功
expect(wrapper.vm.userInfo).toEqual({
name: 'John Doe',
phone: '1234567890',
uuid: 'abc-123',
});
});
it('测试修改个人信息', async () => {
const btn = wrapper.find('[data-testid="editInfo"]')//找到修改的弹框按钮
await btn.trigger('click')//点击
expect(wrapper.vm.UserInfoDialogVisible).toBeTruthy()//判断expect里面的值是否等于true,也就是点击按钮显示这个修改弹框
// 确保弹框组件完全渲染
await wrapper.vm.$nextTick()
//const phone = wrapper.find('[data-testid="phone"]')//这里找到phone的input框
// await phone.setValue(18482238166); 这是el-input的数据不能通过setValue添加,如果是普通input则可以使用
// 所以我们利用wrapper.vm拿到组件的userInfo属性,为其添加值
wrapper.vm.userInfo.name = '邢捕头';
wrapper.vm.userInfo.phone = '18482238166';
// 现在检查值是否更新,这里成功那么我们输入框就确定添加上去值了
expect(wrapper.vm.userInfo.phone).toBe('18482238166');
//这里我们找到确认按钮,并且点击
const submitBtn = wrapper.find('[data-testid="submitForm"]')
await submitBtn.trigger('click')
//mock请求并且传入成功返回的值
spyPost.mockResolvedValue(wrapper.vm.userInfo);
//这边的逻辑就是
expect(wrapper.vm.userInfo).toEqual({
name: '邢捕头',
phone: '18482238166',
uuid: 'abc-123',
});
})
})
这里是打印的spy
这些函数主要用于对 JavaScript 函数进行模拟(mock)
- getMockName:获取 mock 函数的名称。
- mockName:为 mock 函数设置一个新的名称。
- mockClear:清除 mock 函数的调用记录和返回值信息。
- mockReset:重置 mock 函数,包括调用记录和返回值,回到初始状态。
- mockRestore:将 mock 函数恢复为原始实现。
- getMockImplementation:获取 mock 函数的实现。
- mockImplementation:指定 mock 函数的实现,用于替代默认行为。
- mockImplementationOnce:为 mock 函数指定一次性实现,下次调用将使用默认行为。
- withImplementation:链式调用,用于指定 mock 函数的实现。
- mockReturnThis:使 mock 函数返回自身(this),通常用于链式调用。
- mockReturnValue:指定 mock 函数在每次调用时返回的值。
- mockReturnValueOnce:为 mock 函数指定一次性返回值,之后将返回默认值。
- mockResolvedValue:指定 mock 函数返回一个 Promise,并在解析时返回指定值。
- mockResolvedValueOnce:为 mock 函数指定一次性解析值的 Promise。
- mockRejectedValue:指定 mock 函数返回一个被拒绝的 Promise,拒绝时返回指定值。
- mockRejectedValueOnce:为 mock 函数指定一次性拒绝值的 Promise