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

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。


原因分析
  1. <script setup>的闭包特性:<script setup>里定义的函数是组件闭包内的私有变量,通过defineExpose暴露的是Vue创建的代理函数,而非原始函数。你Spy的是这个代理,而事件触发时调用的是原始的updateModelHandler,所以Spy无法捕获到调用。
  2. 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这个导出的函数:

  1. 创建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);
};
  1. 修改组件代码导入该函数:
<script setup lang="ts">
// ... 其他导入
import { updateModelHandler } from './handlers';

// ... 其他代码不变

defineExpose({updateModelHandler});
</script>
  1. 测试代码中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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 03:23:23