框架与库
- 使用 TypeScript 作为主要开发语言
- 使用 Vue 3 + Composition API
- 使用 quasar 2.15.1
- 测试工具:vitest + Vue Test Utils
需要安装的依赖
- @quasar/quasar-app-extension-testing-unit-vitest // quasar测试集成
- @quasar/vite-plugin // quasar插件注入
- @vitest/coverage-v8 // 支持检测代码覆盖率
- @vitest/ui // Vitest 的 UI 界面
- @vitejs/plugin-vue
- @vue/test-utils // 集成测试工具库
- vitest // 测试框架
- vite-tsconfig-paths // 提供对 TypeScript 路径映射
根目录配置(vitest.config.ts)
import { defineConfig } from 'vitest/config' // Vitest 提供的工具函数,用于定义配置对象。
import vue from '@vitejs/plugin-vue' // Vite 的 Vue 插件,支持 .vue 文件的解析和编译。
import { quasar } from '@quasar/vite-plugin' // Quasar 框架的 Vite 插件,用于集成 Quasar 组件库和样式。
import tsconfigPaths from 'vite-tsconfig-paths' // Vite 插件,支持通过 tsconfig.json 配置路径别名。
export default defineConfig({
plugins: [
vue(),
quasar({
sassVariables: 'src/css/quasar.variables.sass',
}),
tsconfigPaths({
// This is needed to avoid Vitest picking up tsconfig.json files from other unrelated projects in the monorepo
ignoreConfigErrors: true,
}),
],
test: {
globals: true, // 启用全局变量(如 describe、it、expect),无需手动导入测试工具
environment: 'happy-dom', // 使用 Happy DOM 作为测试环境,模拟浏览器 DOM 和 API。
setupFiles: 'test/vitest/vitest.setup.ts', // 指定测试运行前加载的文件,通常用于初始化全局状态或引入 mock 数据。
include: [ // 定义需要测试的文件路径模式。
'src/**/*/*.{spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
'test/vitest/__tests__/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
],
coverage: {
provider: 'v8', // 使用 V8 引擎提供的代码覆盖率计算工具。
reporter: ['text', 'json', 'html'], // 指定生成的覆盖率报告格式
},
},
})
测试用例编写
目录结构
├─ test
│ └─ vitest
│ ├─ vitest.setup.ts
│ └─ __tests__
│ └─ InputNumber.spec.ts
测试定义
describe('描述测试组', () => { /* 测试用例 */ });// 定义一个测试用例(it 和 test 等效)it('测试描述', () => { /* 测试逻辑 */ });test('测试描述', () => { /* 测试逻辑 */ });// 并发测试(同时运行多个测试用例)it.concurrent('并发测试', async () => { /* 异步逻辑 */ });
生命周期钩子
beforeAll(() => { /* 所有测试前执行 */ });
afterAll(() => { /* 所有测试后执行 */ });
beforeEach(() => { /* 每个测试前执行 */ });
afterEach(() => { /* 每个测试后执行 */ });
断言
-
断言方法支持链式调用(如
.not.toBe())。expect(value).toBe(expected); // 严格相等(===) expect(value).toEqual(expected); // 递归深度比较对象/数组 expect(value).toContain(item); // 断言实际值是否在数组中。检查一个字符串是否是另一个字符串的子串。如果你在类似浏览器的环境中运行测试,这个断言还可以检查类是否包含在 classList 中,或者一个元素是否在另一个元素内部。 expect(value).toBeTruthy(); // 断言值在转换为布尔值时为 true expect(value).toBeNull(); // 检查提供的类型是否为 null expect(value).toBeUndefined(); // 检查提供的类型是否为 undefined expect(value).toBeGreaterThan(3); // 断言实际值是否大于接收值 expect(value).toThrow(error); // 函数抛出错误 expect(value).toMatchSnapshot(); // 快照测试
模拟函数
使用 vi.mock() 时,Vitest 会自动提升(hoist)到文件顶部,需注意模块加载顺序。
// 创建一个模拟函数
const mockFn = vi.fn();
// 模拟模块
vi.mock('模块路径', () => ({ /* 模拟实现 */ }));
// 模拟对象方法
vi.spyOn(object, 'method').mockImplementation(() => { /* 模拟逻辑 */ });
// 重置模拟函数状态
mockFn.mockReset();
// 验证模拟函数调用
expect(mockFn).toHaveBeenCalled(); // 是否被调用
expect(mockFn).toHaveBeenCalledWith(arg1, arg2); // 调用参数匹配
异步测试
// 使用 async/await
test('异步测试', async () => {
const data = await fetchData();
expect(data).toBe('result');
});
// 使用回调函数
test('回调测试', (done) => {
fetchData((data) => {
expect(data).toBe('result');
done(); // 标记异步完成
});
});
配置工具
// 动态配置测试(如设置超时时间)
test.configure({ timeout: 1000 })('配置测试', () => { /* ... */ });
// 模拟时间(用于测试定时器)
vi.useFakeTimers();
vi.advanceTimersByTime(1000); // 快进时间
vi.useRealTimers(); // 恢复真实时间
其他常用 API
// 参数化测试(多组输入输出)
test.each([
[1, 1, 2],
[2, 3, 5],
])('加法测试 %i + %i = %i', (a, b, expected) => {
expect(a + b).toBe(expected);
});
// 仅运行此测试(调试用)
test.only('仅运行此测试', () => { /* ... */ });
// 跳过测试
test.skip('跳过此测试', () => { /* ... */ });