Vue 3 组件测试实战:用 Vitest 与 @vue/test-utils 验证交互与逻辑

test12026-09-3011 次阅读

为什么给组件写测试

组件测试介于单元测试和端到端测试之间:它不依赖真实浏览器,只渲染单个组件并验证交互、props、emit 和渲染结果。相比 E2E 更快更稳,相比纯函数测试更贴近真实使用。

搭建测试环境

Vue 3 生态首选 Vitest,配合 @vue/test-utils 的 mount 与 shallowMount 来挂载组件。

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

// vitest.config.ts
import { defineConfig } from "vitest/config";
import vue from "@vitejs/plugin-vue";

export default defineConfig({
  plugins: [vue()],
  test: {
    environment: "jsdom",
    globals: true,
  },
});

测试一个计数器组件

先写一个最小计数器组件,点击按钮计数加一并抛出 change 事件,再针对它的行为进行断言。

<!-- Counter.vue -->
<script setup lang="ts">
import { ref } from "vue";
const count = ref(0);
const emit = defineEmits<{ change: [number] }>();
function inc() {
  count.value++;
  emit("change", count.value);
}
</script>

<template>
  <button data-test="inc" @click="inc">次数:{{ count }}</button>
</template>
// counter.test.ts
import { describe, it, expect } from "vitest";
import { mount } from "@vue/test-utils";
import Counter from "@/components/Counter.vue";

describe("Counter", () => {
  it("点击后计数 +1 并抛出 change 事件", async () => {
    const wrapper = mount(Counter);
    await wrapper.get('[data-test="inc"]').trigger("click");
    expect(wrapper.text()).toContain("次数:1");
    expect(wrapper.emitted("change")?.[0]).toEqual([1]);
  });
});

测试异步渲染

组件里有接口请求时,用 flushPromises 等微任务跑完,再断言渲染结果。

import { flushPromises, mount } from "@vue/test-utils";
import UserCard from "@/components/UserCard.vue";

it("加载后渲染用户名", async () => {
  const wrapper = mount(UserCard, {
    props: { id: 1 },
  });
  await flushPromises();
  expect(wrapper.text()).toContain("张三");
});

几个常用技巧

  • 用 data-test 这类属性选择器定位元素,比依赖 class 更稳,重构样式也不影响测试。
  • 触发事件用 trigger,返回 Promise,记得 await,否则断言会先于 DOM 更新执行。
  • 只测行为不测实现:关注「点击后发生了什么」,而不是内部 ref 怎么变。
  • 慢组件可以用 shallowMount 把子组件替成桩,隔离被测对象。
T

test1

文章作者

为什么给组件写测试 组件测试介于单元测试和端到端测试之间:它不依赖真实浏览器,只渲染单个组件并验证交互、props、e...

分类
技术
发布时间
2026-09-30
字数
约 1803 字
阅读
11 次