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
相关产品推荐
相关产品推荐

