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

Vitest与Vue-Test-Utils测试组件内函数调用失败问题

Vue3组件方法点击触发测试失败解决方案

问题背景

在Vue3项目的<script setup>组件中,测试按钮点击是否触发内部example函数时,始终收到「函数未被调用」的报错,且覆盖率报告显示该函数未被测试。

组件代码(Example.vue)

<script setup lang="ts">
const example = () => console.log("TEST");
</script>

<template>
  <div>
    <button data-testid="button-example" @click="example">Click</button>
  </div>
</template>

测试代码(example.spec.ts)

import { describe, vi, it, beforeEach, expect } from "vitest";
import { mount, VueWrapper } from "@vue/test-utils";

import Example from "../src/components/Example.vue";

describe("example testing", () => {
  let wrapper: VueWrapper<Example & { [key: string]: any }>;

  beforeEach(() => {
    wrapper = mount(Example);
  });

  it("component mounted", () => {
    expect(wrapper.exists()).toBeTruthy();
  });

  it("example function should be called", async () => {
    const example = vi.spyOn(wrapper.vm, "example");
    await wrapper.find('[data-testid="button-example"]').trigger("click");

    expect(example).toHaveBeenCalled();
  });
});

报错信息

FAIL  test/example.spec.ts > example testing > example function should be called
AssertionError: expected "example" to be called at least once
 ❯ test/example.spec.ts:21:21
     19|     await wrapper.find('[data-testid="button-example"]').trigger("click");
     20| 
     21|     expect(example).toHaveBeenCalled();
       |                     ^
     22|   });
     23| });

已尝试的无效方法

  1. 先触发点击再创建spy:
it("example function should be called", async () => {
  await wrapper.find('[data-testid="button-example"]').trigger("click");
  const example = vi.spyOn(wrapper.vm, "example");

  expect(example).toHaveBeenCalled();
});

报错:This function is not spy or call spy!

  1. 直接断言实例方法:
it("example function should be called", async () => {
  await wrapper.find('[data-testid="button-example"]').trigger("click");

  expect(wrapper.vm.example).toHaveBeenCalled();
});

报错:This function is not spy or call spy!

解决方案

方案1:将方法暴露到组件实例

<script setup>中声明的方法默认是私有的,不会挂载到组件实例上,需要通过defineExpose主动导出:
修改Example.vue:

<script setup lang="ts">
const example = () => console.log("TEST");
defineExpose({ example }); // 新增此行,暴露方法到组件实例
</script>

<template>
  <div>
    <button data-testid="button-example" @click="example">Click</button>
  </div>
</template>

此时原测试代码即可正常运行,spyOn能够正确捕获到方法调用。

方案2:测试方法的副作用(无需暴露方法)

如果不想修改组件代码,可以测试方法执行后的副作用。比如本例中方法会打印日志,可通过监听console.log来验证:
修改测试用例:

it("example function should be called", async () => {
  // 监听console.log并mock实现
  const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
  
  await wrapper.find('[data-testid="button-example"]').trigger("click");
  
  // 断言日志是否被正确打印
  expect(logSpy).toHaveBeenCalledWith("TEST");
  
  // 恢复console.log的原始实现
  logSpy.mockRestore();
});

原因说明

<script setup>是Vue3的语法糖,其内部声明的变量、方法默认处于封闭作用域,不会自动暴露给组件实例(wrapper.vm)。因此直接通过wrapper.vm.example获取到的引用,和模板中绑定的example函数不是同一个,导致spy无法捕获到调用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 02:31:30