初始配置
使用到的依赖如下
"@vitest/ui": "^1.6.0",
"@vue/test-utils": "^2.4.6",
"jsdom": "^24.0.0",
"vitest": "^1.6.0",
"flush-promises": "^1.0.2",
在tsconfig.json中配置
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "Node",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"jsxImportSource": "vue",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"paths": {
"@/*": ["./src/*"],
"_c/*": ["./src/components"]
},
"allowSyntheticDefaultImports": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue","tests/**/*.ts","tests/**/*.tsx"],
"references": [{ "path": "./tsconfig.node.json" }]
}
新增vitest.config.js配置
import { fileURLToPath } from 'node:url'
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
import viteConfig from './vite.config'
export default mergeConfig(
viteConfig,
defineConfig({
test: {
environment: 'jsdom',
exclude: [...configDefaults.exclude, 'e2e/**'],
root: fileURLToPath(new URL('./', import.meta.url)
)
}
})
)
实践一下
新增两个组件 HelloWorld.vue
<template>
<h1>{{msg}}</h1>
<button @click="setCount">{{count}}</button>
<input type="text" v-model="todo"/>
<button class="addTodo" @click="addTodo">add</button>
<button class="loadUser" @click="loadUser">load</button>
<p v-if="user.loading" class="loading">Loading</p>
<div v-else class="userName">{{user.data && user.data.username}}</div>
<p v-if="user.error" class="error">error!</p>
<ul>
<li v-for="(todo, index) in todos" :key="index">{{todo}}</li>
</ul>
<hello msg="1234"></hello>
</template>
<script lang="ts">
import { defineComponent, reactive, ref } from 'vue'
import Hello from './Hello.vue'
import axios from 'axios'
export default defineComponent({
name: 'HelloWorld',
components: {
Hello
},
props: {
msg: String
},
emits: ['send'],
setup(props, context) {
const todo = ref('')
const todos = ref<string[]>([])
const user = reactive({
data: null as any,
loading: false,
error: false
})
const count = ref(1)
const setCount = () => {
count.value++
}
const addTodo = () => {
if (todo.value) {
todos.value.push(todo.value)
context.emit('send', todo.value)
}
}
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
})
}
return {
count,
setCount,
todo,
todos,
addTodo,
user,
loadUser,
}
}
})
</script>
Hello.vue
<template>
<h1 class="hello">{{msg}}</h1>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'Hello',
props: {
msg: String
}
})
</script>
新建测试文件tests/unit/example.spec.ts
import { shallowMount, VueWrapper } from '@vue/test-utils'
import axios from 'axios'
import flushPromises from 'flush-promises'
import HelloWorld from '@/components/HelloWorld.vue'
import Hello from '@/components/Hello.vue'
import {vi,describe,it,beforeAll,afterEach,expect} from "vitest"
vi.mock('axios')
// 强制类型转换,以确保 mock 后的 Axios 保持类型信息
const mockAxiosGet = vi.spyOn(axios, 'get')
const msg = 'new message'
let wrapper: VueWrapper<any>
describe('HelloWorld.vue', () => {
beforeAll(() => {
wrapper = shallowMount(HelloWorld, {
props: { msg }
})
})
it('renders props.msg when passed', () => {
expect(wrapper.get('h1').text()).toBe(msg)
expect(wrapper.findComponent(Hello).exists()).toBeTruthy()
})
it('should update the count when clicking the button', async () => {
await wrapper.get('button').trigger('click')
expect(wrapper.get('button').text()).toBe('2')
})
it('should add todo when fill the input and click the add button', async () => {
const todoContent = 'buy milk'
await wrapper.get('input').setValue(todoContent)
expect(wrapper.get('input').element.value).toBe(todoContent)
await wrapper.get('.addTodo').trigger('click')
expect(wrapper.findAll('li')).toHaveLength(1)
expect(wrapper.get('li').text()).toBe(todoContent)
console.log(wrapper.emitted())
expect(wrapper.emitted()).toHaveProperty('send')
const events = wrapper.emitted('send')
expect(events && events[0]).toEqual([todoContent])
})
it('should load user message when click the load button', async () => {
mockAxiosGet.mockResolvedValueOnce({ data: { username: 'viking'}})
await wrapper.get('.loadUser').trigger('click')
expect(mockAxiosGet).toHaveBeenCalled()
expect(wrapper.find('.loading').exists()).toBeTruthy()
await flushPromises()
// 界面更新完毕
console.log(1111,wrapper.find('.userName').text())
expect(wrapper.find('.loading').exists()).toBeFalsy()
expect(wrapper.get('.userName').text()).toBe('viking')
})
it('should load error when return promise reject', async () => {
mockAxiosGet.mockRejectedValueOnce('error')
await wrapper.get('.loadUser').trigger('click')
expect(mockAxiosGet).toHaveBeenCalledTimes(1)
await flushPromises()
expect(wrapper.find('.loading').exists()).toBe(false)
expect(wrapper.find('.error').exists()).toBe(true)
})
afterEach(() => {
mockAxiosGet.mockReset()
})
})
在package.json中配置 或者在控制台直接运行vitest
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"preview": "vite preview",
"test:unit": "vitest"
},