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

Vue Test Utils/apollo-composable 如何检测useMutation是否被调用

测试AddEmployeeModal.vue时Mock@vue/apollo-composable的useMutation方案

针对第三方ES模块导出的函数,直接用测试框架(Vitest/Jest)自带的模块mock能力即可实现,不需要额外工具。


基础实现(以Vitest为例,Jest逻辑完全一致)

先mock第三方模块,替换useMutation为可控的mock函数,不需要mock的导出可以保留原模块的真实实现。
注意:你当前组件代码把provideApolloClient、useMutation调用写在了script顶层,mock声明必须写在导入被测组件之前,否则模块加载时会先拿到原始未mock的函数,导致mock失效。

import { describe, it, expect, vi, beforeEach } from 'vitest'

// 1. 先声明mock,必须在导入组件前执行
const mockMutate = vi.fn()
vi.mock('@vue/apollo-composable', () => {
  // 引入原模块的真实导出,只替换需要mock的部分
  const actualApolloComposables = vi.importActual('@vue/apollo-composable')
  return {
    ...actualApolloComposables,
    // mock useMutation,返回组件用到的属性
    useMutation: vi.fn(() => ({
      mutate: mockMutate,
      loading: { value: false },
      error: { value: null },
      onDone: vi.fn(),
      onError: vi.fn()
    })),
    // provideApolloClient直接mock为空函数,避免初始化真实apollo客户端
    provideApolloClient: vi.fn()
  }
})

// 2. mock完成后再导入组件和测试工具
import { mount } from '@vue/test-utils'
import AddEmployeeModal from './AddEmployeeModal.vue'
import { CreateEmployeeDocument, UpdateEmployeeDocument } from '@/generated/graphql'

describe('AddEmployeeModal', () => {
  beforeEach(() => {
    // 每个用例前清空调用记录,避免用例间干扰
    vi.clearAllMocks()
  })

  it('组件初始化时传入正确的GraphQL文档调用useMutation', () => {
    mount(AddEmployeeModal)
    // 校验useMutation调用次数和入参
    expect(useMutation).toHaveBeenCalledTimes(2)
    expect(useMutation).toHaveBeenCalledWith(CreateEmployeeDocument)
    expect(useMutation).toHaveBeenCalledWith(UpdateEmployeeDocument)
  })

  it('触发提交逻辑时正确调用mutate方法', async () => {
    const wrapper = mount(AddEmployeeModal)
    // 模拟组件内的提交操作,比如点击提交按钮
    await wrapper.find('button.submit-btn').trigger('click')
    // 校验mutate被调用,也可以校验传入的参数
    expect(mockMutate).toHaveBeenCalledWith(expect.objectContaining({
      // 这里写你预期传给mutate的参数
    }))
  })
})

进阶:区分不同Mutation的Mock实例

上面的写法所有useMutation调用返回同一个mockMutate,如果要分别校验新增、更新两个mutation的调用逻辑,可以在mock时根据传入的Document返回不同的mock函数:

// 分别声明两个mutate的mock
const mockAddEmployee = vi.fn()
const mockUpdateEmployee = vi.fn()

vi.mock('@vue/apollo-composable', () => {
  const actualApolloComposables = vi.importActual('@vue/apollo-composable')
  return {
    ...actualApolloComposables,
    useMutation: vi.fn((gqlDocument) => {
      // 根据传入的GraphQL文档返回对应的mock实例
      switch(gqlDocument) {
        case CreateEmployeeDocument:
          return {
            mutate: mockAddEmployee,
            loading: { value: false },
            error: { value: null }
          }
        case UpdateEmployeeDocument:
          return {
            mutate: mockUpdateEmployee,
            loading: { value: false },
            error: { value: null }
          }
        default:
          return {
            mutate: vi.fn(),
            loading: { value: false },
            error: { value: null }
          }
      }
    }),
    provideApolloClient: vi.fn()
  }
})

这种写法可以在测试用例里单独断言mockAddEmployee、mockUpdateEmployee的调用情况,不会出现逻辑混淆。


注意事项

  • 不要全量mock整个第三方模块,通过importActual/requireActual保留原模块的真实导出,只替换需要mock的部分,避免破坏其他依赖逻辑。
  • 如果组件中用到了useQuery,mock逻辑和useMutation一致,按需补全返回值里组件用到的属性(比如result、refetch等)即可。
  • 如果使用Jest测试,只需要把代码里的vi替换为jest即可:vi.mock换成jest.mock,vi.fn换成jest.fn,vi.importActual换成jest.requireActual,vi.clearAllMocks换成jest.clearAllMocks,API逻辑完全一致。
  • 模块mock声明必须放在导入被测组件之前,ES模块为静态加载,导入组件时会立即执行script顶层代码,后置mock不会生效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 20:15:44