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

