记一次 Vue 3 + quasar 2 单元测试

87 阅读3分钟

框架与库

  • 使用 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('跳过此测试', () => { /* ... */ });