Tailwind CSSTailwind CSS
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
  • 单元测试:Vitest 与 Vue Test Utils

单元测试:Vitest 与 Vue Test Utils

单元测试是确保 Vue 3 项目代码质量的重要环节,能够验证组件逻辑、状态管理和交互行为的正确性。Vitest 和 Vue Test Utils 是 Vue 3 生态中推荐的测试工具,前者提供快速的测试运行环境,后者为测试 Vue 组件提供便捷的 API。本节将介绍这两者的安装、配置和基本使用方法,并通过示例展示如何进行单元测试,帮助你建立可靠的测试流程。

什么是单元测试?

单元测试是对代码最小单元(如函数、组件)的独立测试,旨在验证其在各种输入下的行为是否符合预期。在 Vue 3 中,单元测试通常针对组件的逻辑和渲染结果。

为什么使用 Vitest 和 Vue Test Utils?

  • Vitest:
    • 由 Vite 团队开发,基于 Vite 的快速构建能力。
    • 支持 ESM、TypeScript 和 HMR。
    • 与 Jest 兼容,易于迁移。
  • Vue Test Utils:
    • 官方测试工具,提供组件挂载、模拟和断言 API。
    • 支持Vue 3 的 Composition API。

安装与配置

安装

在 Vue 3 项目中添加测试依赖:

npm install -D vitest @vue/test-utils jsdom happy-dom

配置 Vitest

vite.config.ts

// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  test: {
    environment: 'jsdom', // 模拟浏览器环境
    globals: true, // 全局 API(如 describe、it)
    setupFiles: './tests/setup.ts' // 全局配置
  }
});

tests/setup.ts

// tests/setup.ts
import { config } from '@vue/test-utils';

// 全局配置(可选)
config.global.mocks = {
  $t: (msg: string) => msg // 模拟 i18n
};

package.json

{
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest"
  }
}

项目结构

src/
├── components/
│   └── Counter.vue
├── tests/
│   ├── setup.ts
│   └── Counter.test.ts
├── vite.config.ts
└── package.json

基本使用

测试组件:Counter.vue

<!-- src/components/Counter.vue -->
<template>
  <div>
    <p>计数: {{ count }}</p>
    <button @click="increment">增加</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';

export default defineComponent({
  name: 'Counter',
  props: {
    initial: {
      type: Number,
      default: 0
    }
  },
  setup(props) {
    const count = ref(props.initial);
    const increment = () => count.value++;
    return { count, increment };
  }
});
</script>

编写单元测试

Counter.test.ts

// tests/Counter.test.ts
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import Counter from '../components/Counter.vue';

describe('Counter.vue', () => {
  it('renders initial count', () => {
    const wrapper = mount(Counter, {
      props: { initial: 5 }
    });
    expect(wrapper.text()).toContain('计数: 5');
  });

  it('increments count when button is clicked', async () => {
    const wrapper = mount(Counter);
    const button = wrapper.find('button');
    await button.trigger('click');
    expect(wrapper.text()).toContain('计数: 1');
  });

  it('has correct component name', () => {
    const wrapper = mount(Counter);
    expect(wrapper.vm.$options.name).toBe('Counter');
  });
});

运行测试

npm run test
  • 输出:
    ✓ tests/Counter.test.ts (3)
      ✓ Counter.vue
        ✓ renders initial count
        ✓ increments count when button is clicked
        ✓ has correct component name
    

实时测试

npm run test:watch
  • 效果:修改代码后自动重新运行测试。

核心测试技巧

1. 测试 Props

it('renders with custom initial value', () => {
  const wrapper = mount(Counter, {
    props: { initial: 10 }
  });
  expect(wrapper.text()).toContain('计数: 10');
});

2. 测试事件

it('emits event on increment', async () => {
  const wrapper = mount(Counter);
  await wrapper.vm.increment();
  expect(wrapper.emitted('increment')).toBeUndefined(); // 无自定义事件
  expect(wrapper.text()).toContain('计数: 1');
});
  • 扩展:若需要测试自定义事件,添加 emits:
    <script lang="ts">
    export default defineComponent({
      emits: ['increment'],
      setup(props, { emit }) {
        const count = ref(0);
        const increment = () => {
          count.value++;
          emit('increment', count.value);
        };
        return { count, increment };
      }
    });
    </script>
    

3. 测试异步逻辑

<!-- AsyncComp.vue -->
<template>
  <p>{{ data }}</p>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';

export default defineComponent({
  async setup() {
    const data = ref<string>('');
    const response = await new Promise<string>(resolve => 
      setTimeout(() => resolve('Loaded'), 100)
    );
    data.value = response;
    return { data };
  }
});
</script>
// tests/AsyncComp.test.ts
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import AsyncComp from '../components/AsyncComp.vue';

describe('AsyncComp.vue', () => {
  it('renders async data', async () => {
    const wrapper = mount(AsyncComp);
    expect(wrapper.text()).toBe(''); // 初始状态
    await new Promise(resolve => setTimeout(resolve, 150)); // 等待异步
    expect(wrapper.text()).toBe('Loaded');
  });
});

4. 模拟用户交互

it('updates count on multiple clicks', async () => {
  const wrapper = mount(Counter);
  const button = wrapper.find('button');
  await button.trigger('click');
  await button.trigger('click');
  expect(wrapper.text()).toContain('计数: 2');
});

高级技巧

1. Mocking 依赖

  • 模拟全局对象:
    it('uses mocked $t', () => {
      const wrapper = mount(Counter, {
        global: {
          mocks: { $t: (msg: string) => `Translated: ${msg}` }
        }
      });
      expect(wrapper.vm.$t('test')).toBe('Translated: test');
    });
    

2. 测试 Pinia Store

// src/stores/counter.ts
import { defineStore } from 'pinia';
import { ref } from 'vue';

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0);
  const increment = () => count.value++;
  return { count, increment };
});
// tests/CounterWithStore.test.ts
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import { createPinia, setActivePinia } from 'pinia';
import Counter from '../components/Counter.vue';

describe('Counter with Pinia', () => {
  it('uses store count', async () => {
    setActivePinia(createPinia());
    const wrapper = mount(Counter);
    const button = wrapper.find('button');
    await button.trigger('click');
    expect(wrapper.text()).toContain('计数: 1');
  });
});

注意事项

  1. 环境配置:
    • jsdom 模拟 DOM,复杂交互可能需 happy-dom。
  2. 异步测试:
    • 确保等待异步操作完成(如 await wrapper.vm.$nextTick())。
  3. 覆盖率:
    • 添加 --coverage 查看测试覆盖率:
      npm run test -- --coverage
      

总结

Vitest 和 Vue Test Utils 为 Vue 3 提供了快速、强大的单元测试能力。通过本节,你学会了安装配置、测试组件逻辑和交互,并掌握了高级技巧如 Mocking 和 Pinia 集成。这些技能确保了代码的健壮性。下一节将探讨 E2E 测试,带你进入更全面的测试领域!

Last Updated:: 2/24/25, 3:35 PM