You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3组合式API场景下,如何用shallowMount测试emit触发逻辑?

Vue组件事件触发测试问题解决方案

项目结构

/App.vue
|-components/ComponentA.vue
|-components/ComponentB.vue

业务逻辑

在App.vue中,ComponentB需等待ComponentA触发onMessage事件后才会挂载,核心代码如下:

<!-- App.vue -->
<template>
  <ComponentA @onMessage="onMessage"/>
  <ComponentB v-if="message"/>
</template>

<script setup lang="ts">
import { type Ref, ref } from 'vue';
import ComponentA from '@/components/ComponentA.vue';
import ComponentB from '@/components/ComponentB.vue';

const message: Ref<String | undefined> = ref();

const onMessage = (messageParam: String): void => {
  message.value = messageParam;
};
</script>

测试遇到的问题

编写测试验证上述逻辑时,发现trigger方法仅支持DOM事件,无法触发组件自定义emit,导致测试失败。测试代码如下:

// App.spec.ts
import { describe, it, expect } from 'vitest'

import { shallowMount } from '@vue/test-utils'
import App from '@/App.vue'
import ComponentA from '@/components/ComponentA.vue'
import ComponentB from '@/components/ComponentB.vue'
import { nextTick } from 'vue'

describe('App', () => {
  it("renders the ComponentA component", (): void => {
    const wrapper = shallowMount<App>(App);
    expect(wrapper.findComponent(ComponentA).exists()).toBe(true);
  }); // -> 正常运行

  it("does not render the ComponentB component", (): void => {
    const wrapper = shallowMount<App>(App);
    expect(wrapper.findComponent(ComponentB).exists()).toBe(false);
  }); // -> 正常运行

  it('renders ComponentB when event emitted', async () => {
    const wrapper = shallowMount<App>(App);
    wrapper.findComponent(ComponentA).trigger('onMessage', 'MESSAGE');
    await nextTick();
    expect(wrapper.findComponent(ComponentB).exists()).toBe(true);
  }) // -> 测试失败
})

组件完整代码

ComponentA

<!-- Component A -->
<template>
  <h2>Component A</h2>
</template>

<script setup lang="ts">
import { onMounted } from 'vue';

const emit = defineEmits<{
  onMessage: [message: string]
}>();

onMounted(async () => {
  setTimeout(() => {
    emit("onMessage", "Loading finished!");
  }, 1000);
});
</script>

ComponentB

<!-- Component B -->
<template>
  <h2>Component B</h2>
</template>

解决方案

方法1:直接调用组件实例的emit方法

trigger仅支持DOM事件,对于组件自定义事件,需直接调用组件实例的emit方法触发:

it('renders ComponentB when event emitted', async () => {
  const wrapper = shallowMount<App>(App);
  // 获取ComponentA实例并手动触发事件
  wrapper.findComponent(ComponentA).vm.emit('onMessage', 'MESSAGE');
  await nextTick();
  expect(wrapper.findComponent(ComponentB).exists()).toBe(true);
})

方法2:模拟定时器验证真实流程

若要测试ComponentA的完整逻辑(包括setTimeout延迟触发事件),可使用vitest的假定时器加速测试:

it('renders ComponentB after ComponentA\'s timeout emits event', async () => {
  vi.useFakeTimers(); // 启用假定时器
  const wrapper = shallowMount<App>(App);
  
  vi.runAllTimers(); // 执行所有待处理的定时器任务
  await nextTick(); // 等待DOM更新
  
  expect(wrapper.findComponent(ComponentB).exists()).toBe(true);
  vi.useRealTimers(); // 恢复真实定时器
})

方法3:存根ComponentA控制事件触发

通过存根ComponentA,完全控制事件触发时机,专注测试App.vue的逻辑:

it('renders ComponentB when stubbed ComponentA emits event', async () => {
  const wrapper = shallowMount<App>(App, {
    global: {
      stubs: {
        ComponentA: {
          template: '<div></div>',
          emits: ['onMessage'] // 声明组件支持的事件
        }
      }
    }
  });
  
  // 触发存根组件的onMessage事件
  wrapper.findComponent(ComponentA).vm.$emit('onMessage', 'MESSAGE');
  await nextTick();
  
  expect(wrapper.findComponent(ComponentB).exists()).toBe(true);
})

内容的提问来源于stack exchange,提问作者fguillen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 03:43:22