Vitest单元测试框架
基本概况
Vitest 是由 Vite 驱动的下一代测试框架。
命名规范
只要满足规范一或规范二即可。
- 规范一: 与业务文件独立开来,新建文件夹__test__,单独存放测试用例
- 规范二: 与业务文件放在一起,例如业务文件为index.ts,则测试用例文件为index.test.ts或index.spec.ts
# 文件夹
__tests__
# 文件名称
.test.ts .test.js
.spec.ts .spec.js
安装配置
vite.config.js
Vitest 2或以下版本需要使用 三斜杠指令 在你的配置文件的顶部引用。
/// <reference types="vitest" />
// 第三方资源类库或插件
import { defineConfig, loadEnv } from 'vite';
/**
* 深度自定义配置
*/
export default defineConfig((params) => {
const { mode, command, ssrBuild } = params || {};
// 获取环境变量
const envDir = path.resolve(__dirname, './env');
const nodeEnv = loadEnv(mode, envDir);
const configOptions = {
envDir,
base: "/",
};
/**
* 单元测试
* Vitest 是由 Vite 驱动的下一代测试框架。
*/
configOptions.test = {};
return configOptions;
});
package.json
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:unit": "vitest --environment jsdom --root src/"
},
"devDependencies": {
"vitest": "^2.0.5"
}
}
执行指令
- 执行所有测试用例
> npm run test
- 选择性执行部分测试用例
vitest basic
# 执行以下带有basci字符串的用例
basic.test.ts
basic-foo.test.ts
basic/foo.test.ts
- 全名过滤测试用例
# 可以使用 -t, --testNamePattern <pattern> 选项按全名过滤测试
配置大全
/**
* 单元测试
* Vitest 是由 Vite 驱动的下一代测试框架。
*/
configOptions.test = {
globals: true,
environment: "jsdom", // 'node' | 'jsdom' | 'happy-dom' | 'edge-runtime' | string
root: fileURLToPath(new URL('./', import.meta.url)),
// 匹配排除测试文件的 glob 规则。
exclude: ['**/node_modules/**', '**/dist/**', '**/cypress/**', '**/.{idea,git,cache,output,temp}/**', '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*'],
// 匹配包含测试文件的 glob 规则。
include: ['**/*.{test,spec}.?(c|m)[jt]s?(x)'],
includeSource: ['src/**/*.{js,ts}'],
coverage: {
provider: 'istanbul', // or 'v8' 默认使用v8
reporter: ['text', 'html', 'json'],
reporters: ['verbose', 'html'],
branches: 80,
functions: 80,
lines: 80,
statements: -10,
reportsDirectory: './tests/unit/coverage', // 修改输出报告位置
exclude:['src/**/icons'] //不需要单元测试覆盖的地方
},
browser: {
enabled: true,
name: 'chrome', // browser name is required
},
server: {}
}
常见释疑问题
ReferenceError: document is not defined
在vite.config.ts中添加如下配置
/**
* 单元测试
* Vitest 是由 Vite 驱动的下一代测试框架。
*/
configOptions.test = {
environment: "happy-dom"
};
核心API语法
expect属性与方法
代码示例
import { expect } from 'vitest';
const input = Math.sqrt(4);
expect(input).toBe(2);
判断数据类型
import { expect } from 'vitest';
const input = Math.sqrt(4);
expect(input).toBe(2);
测试用例大全
工具函数
- module.test.ts
import { expect, test } from 'vitest';
import { sumUtil } from './index';
test('adds 0 + 0 to equal 0', () => {
expect(sumUtil(0, 0)).toBe(0);
});
test('adds 1 + 2 to equal 3', () => {
expect(sumUtil(1, 2)).toBe(3);
});
- module.ts
// 加法
export function sumUtil(
a: number,
b: number
): number {
return a + b;
}
测试快照
在测试文件的同级目录生成一个文件夹__snapshots__,且文件名称为测试用例文件加上后缀inex.test.ts.snap。
更新快照
当接收到的值与快照不匹配时,测试将失败,并显示它们之间的差异。当需要更改快照时,你可能希望从当前状态更新快照。可以在 CLI 中使用 --update 或 -u 标记使 Vitest 进入快照更新模式。
文本快照
{
"scripts": {
"test": "vitest run",
"test:update": "vitest -u",
"test:watch": "vitest",
"test:unit": "vitest --environment jsdom --root src/"
}
}
- module.test.ts
import { expect, test } from 'vitest';
import { sumUtil } from './index';
test('adds 1 + 2 to equal 3', () => {
expect(sumUtil(1, 2)).toMatchSnapshot();
});
- module.ts
// 加法
export function sumUtil(
a: number,
b: number
): number {
return a + b;
}
- inex.test.ts.snap
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`adds 1 + 2 to equal 3 1`] = `3`;
文件快照
调用 toMatchSnapshot() 时,我们将所有快照存储在格式化的快照文件中。这意味着我们需要转义快照字符串中的一些字符(即双引号 " 和反引号 ```)。同时,你可能会丢失快照内容的语法突出显示(如果它们是某种语言)。为了改善这种情况,我们引入 toMatchFileSnapshot() 以在文件中显式快照。这允许你为快照文件分配任何文件扩展名,并使它们更具可读性。
import { expect, it } from 'vitest'
it('render basic', async () => {
const result = renderHTML(h('div', { class: 'foo' }))
await expect(result).toMatchFileSnapshot('./test/basic.output.html')
});
图像快照
- 安装配置
> npm i -D jest-image-snapshot
- 代码示例
test('image snapshot', () => {
expect(readFileSync('./test/stubs/input-image.png')).toMatchImageSnapshot()
});
vue3单文件组件
- HelloWorld.spec.ts
import { describe, it, expect } from "vitest";
import { mount } from "@vue/test-utils";
import HelloWorld from "./HelloWorld.vue";
describe("HelloWorld", () => {
it("renders properly", () => {
const wrapper = mount(HelloWorld, { props: { msg: "Hello Vitest" } });
expect(wrapper.text()).toContain("Hello Vitest");
});
});
- HelloWorld.vue
<script setup lang="ts">
defineProps<{
msg: string;
}>();
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
You’ve successfully created a project with
<a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
What's next?
</h3>
</div>
</template>
单文件组件一
module.test.ts
import { descrie, expect, it } from "vitest";
import { mount } from "@vue/test-utils";
import moduleView from "./module.vue";
describe('moduleView', () => {
it('renders properly', () => {
const wrapper = mount(moduleView, { props: { name: "单文件组件" } });
// 测试一 等待Vue进行更新循环后,DOM将被渲染
await wrapper.find('button').trigger('click');
// 测试二
expect(wrapper.text()).toContain("单文件组件");
})
})
module.vue
<template>
<section class="page-module">
<p>{{ name }}</p>
</section>
</template>
<script lang="ts" setup>
defineProps({
name: {
type: String,
default() {
return ""
}
}
});
</script>
<style lang="scss" scoped>
.page-module {
position: relative;
}
</style>
单文件组件二
module.test.ts
import { describe, it, expect, vi } from "vitest"
import App from './App.vue'
import { createApp } from 'vue';
import { renderToString } from '@vue/server-renderer';
describe("the main page", ()=>{
it("should not change (Snapshot Test)",async ()=>{
const app = createApp(App);
const date = new Date(2000, 1, 1, 18)
vi.setSystemTime(date)
expect(await renderToString(app)).toMatchSnapshot();
})
})
module.vue
<template>
<section class="page-module">
<p>{{ name }}</p>
</section>
</template>
<script lang="ts" setup>
defineProps({
name: {
type: String,
default() {
return ""
}
}
});
</script>
<style lang="scss" scoped>
.page-module {
position: relative;
}
</style>
- module.test.ts
- module.ts
- module.test.ts
- module.ts
- module.test.ts
- module.ts
- module.test.ts
- module.ts
- module.test.ts
- module.ts
- module.test.ts
- module.ts
- module.test.ts
- module.ts