Vitest中Spy未检测到updateModelHandler被调用的问题排查
问题:Vitest Spy无法识别Vue组件中被调用的updateModelHandler函数
我的Vue 3组件代码(使用<script setup>):
<template> <begin-adult-app-form :form-status="formStatus" :form-submitted="formSubmitted" v-on:form-valid="formInvalidatedHandler" v-on:update-model="updateModelHandler"></begin-adult-app-form> </template> <script setup lang="ts"> import { defineExpose, ref, type Ref } from 'vue'; import { useApplicationStore } from '../stores/applicationStore'; import BeginAdultAppForm from '../components/forms/BeginAdultAppForm.vue'; import type IApplications from '../interfaces/IApplication'; import Application from '../models/Application'; const applicationStore = useApplicationStore(); const application: Ref<IApplications.IApplication> = ref(new Application()); const formStatus: Ref<boolean> = ref(false); const formSubmitted: Ref<boolean> = ref(false); const updateModelHandler = (model: Application): void => { applicationStore.updateApplication(model); }; defineExpose({updateModelHandler}); </script>
对应的Vitest测试代码:
it('it should run the updateModelHandler method when the BeginAdultAppForm invokes the update-model event', async () => { const wrapper = mount(BeginView); const spy = vi.spyOn(wrapper.vm, 'updateModelHandler'); await wrapper.vm.$nextTick(); const formComponent = wrapper.findComponent(BeginAdultAppForm); formComponent.vm.$emit('update-model', new Application()); await wrapper.vm.$nextTick(); expect(spy).toBeCalled(); });
调试时确认updateModelHandler确实执行了,但测试失败,提示expected "updateModelHandler" to be called at least once。
原因分析
<script setup>的闭包特性:<script setup>里定义的函数是组件闭包内的私有变量,通过defineExpose暴露的是Vue创建的代理函数,而非原始函数。你Spy的是这个代理,而事件触发时调用的是原始的updateModelHandler,所以Spy无法捕获到调用。- Spy时机太晚:组件挂载时已经将原始的
updateModelHandler绑定到子组件的update-model事件上,之后再Spywrapper.vm上的函数,只会替换暴露的代理,不会影响已经绑定的原始函数。
解决方案
方案一:Spy业务逻辑依赖(推荐)
既然updateModelHandler最终是调用applicationStore.updateApplication,直接Spy这个方法更贴近实际业务逻辑,也能验证函数的作用:
import { useApplicationStore } from '../stores/applicationStore'; import Application from '../models/Application'; it('should call applicationStore.updateApplication when update-model event is emitted', async () => { // 获取store实例并Spy目标方法 const applicationStore = useApplicationStore(); const updateSpy = vi.spyOn(applicationStore, 'updateApplication'); const wrapper = mount(BeginView); const formComponent = wrapper.findComponent(BeginAdultAppForm); const testModel = new Application(); await formComponent.vm.$emit('update-model', testModel); // 验证方法被调用且参数正确 expect(updateSpy).toHaveBeenCalledWith(testModel); });
方案二:将Handler提取为可导出的函数
把updateModelHandler抽离到单独的模块,这样测试时可以直接Spy这个导出的函数:
- 创建
src/components/handlers.ts:
import { useApplicationStore } from '../stores/applicationStore'; import type Application from '../models/Application'; export const updateModelHandler = (model: Application): void => { const applicationStore = useApplicationStore(); applicationStore.updateApplication(model); };
- 修改组件代码导入该函数:
<script setup lang="ts"> // ... 其他导入 import { updateModelHandler } from './handlers'; // ... 其他代码不变 defineExpose({updateModelHandler}); </script>
- 测试代码中Spy导出的函数:
import * as handlers from '../components/handlers'; import Application from '../models/Application'; it('should call updateModelHandler when update-model event is emitted', async () => { const handlerSpy = vi.spyOn(handlers, 'updateModelHandler'); const wrapper = mount(BeginView); const formComponent = wrapper.findComponent(BeginAdultAppForm); await formComponent.vm.$emit('update-model', new Application()); expect(handlerSpy).toHaveBeenCalled(); });
内容的提问来源于stack exchange,提问作者Joe King
相关产品推荐
相关产品推荐

