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

Last Updated:
Contributors: 709992523, Eshen