无法使用Vitest模拟Vue组合式函数问题求助
如何在Vitest中模拟Vue组合式函数内的内部函数
问题背景
在Vue/Nuxt项目中,我用Composition API实现了useFormData组合式函数,用于表单校验与UI状态控制。现在需要为该函数编写单元测试,核心需求是模拟内部的validationOk函数以覆盖更多测试场景,但尝试了两种方案均未成功。
原组合式函数代码
// ./composables/useFormData.ts import { ref, Ref } from 'vue'; interface IUser { name: string; email: string; } export function useFormData(user: IUser) { const btnColor: Ref<string> = ref('gray'); const enableBtn: Ref<boolean> = ref(false); const errors: Ref<string[]> = ref([]); function validationOk(): boolean { let errMsg = []; if(!user.name) { errMsg.push("User name is required。\n"); } if(!user.email) { errMsg.push("Email is required. \n"); } if(errMsg.length) { errors.value = [...errMsg] } return errMsg.length == 0 ? true : false; } function enableConfirmButton(): void { if(!validationOk()) return; // do nothing // update state value enableBtn.value = true; btnColor.value = 'green'; } return { errors, btnColor, enableBtn, validationOk, enableConfirmButton, } }
失败的尝试方案
方案一:通过vi.mock模拟(未生效)
import {useFormData} from '../../composables/useFormValidation'; import { beforeEach, describe, expect, it, vi } from 'vitest'; vi.mock('../../composables/useFormValidation.ts', async () => { const actual : any = await vi.importActual("../../composables/useFormValidation.ts") return { ...actual, validationOk: vi.fn().mockReturnValue(true) // 未生效 // validationOk: vi.fn(() => true) // 同样未生效 } }) describe('useFormData', () => { beforeEach(() => { vi.clearAllMocks(); vi.resetAllMocks(); }); test('mock validationOk function to forcefully return true', () => { const { btnColor, enableBtn, enableConfirmButton, validationOk } = useFormData({ name: '', email: ''}); expect(validationOk()).toBe(true); // 实际返回原函数结果 }); });
方案二:通过vi.stubGlobal模拟(未生效)
describe('useFormData', () => { beforeEach(() => { vi.clearAllMocks(); vi.resetAllMocks(); }); it('allow mocking inner function', () => { const validationOkMock = vi.fn(() => { return false; // 设置返回false }); vi.stubGlobal('validationOk', validationOkMock); const { btnColor, enableBtn, enableConfirmButton, validationOk } = useFormData({ name: 'aa', email: 'ss@ss.com'}); expect(validationOk()).toBe(false); // 实际返回原函数结果 }) })
正确的解决方法
方法1:重构函数,将校验逻辑抽离为可注入依赖
把validationOk的核心逻辑抽成独立函数,作为可选参数传入useFormData,测试时直接替换为mock函数:
// 修改后的useFormData.ts import { ref, Ref } from 'vue'; interface IUser { name: string; email: string; } // 抽离校验逻辑为独立函数 export function defaultValidation(user: IUser): boolean { let errMsg = []; if(!user.name) errMsg.push("User name is required。\n"); if(!user.email) errMsg.push("Email is required. \n"); return errMsg.length === 0; } export function useFormData(user: IUser, validate = defaultValidation) { const btnColor: Ref<string> = ref('gray'); const enableBtn: Ref<boolean> = ref(false); const errors: Ref<string[]> = ref([]); function validationOk(): boolean { const isValid = validate(user); if(!isValid) { const errMsg = []; if(!user.name) errMsg.push("User name is required。\n"); if(!user.email) errMsg.push("Email is required. \n"); errors.value = [...errMsg]; } return isValid; } function enableConfirmButton(): void { if(!validationOk()) return; enableBtn.value = true; btnColor.value = 'green'; } return { errors, btnColor, enableBtn, validationOk, enableConfirmButton, } }
测试代码:
import { useFormData } from '../../composables/useFormData'; import { describe, expect, it, vi } from 'vitest'; describe('useFormData', () => { it('mock validationOk返回true', () => { const mockValidate = vi.fn().mockReturnValue(true); const { validationOk, enableConfirmButton, enableBtn } = useFormData({ name: '', email: '' }, mockValidate); expect(validationOk()).toBe(true); enableConfirmButton(); expect(enableBtn.value).toBe(true); expect(mockValidate).toHaveBeenCalled(); }); it('mock validationOk返回false', () => { const mockValidate = vi.fn().mockReturnValue(false); const { validationOk, enableConfirmButton, enableBtn } = useFormData({ name: 'aa', email: 'ss@ss.com' }, mockValidate); expect(validationOk()).toBe(false); enableConfirmButton(); expect(enableBtn.value).toBe(false); expect(mockValidate).toHaveBeenCalled(); }); });
方法2:直接mock返回的validationOk函数
利用useFormData返回了validationOk的特性,在测试中直接替换该函数的实现:
import { useFormData } from '../../composables/useFormData'; import { describe, expect, it, vi } from 'vitest'; describe('useFormData', () => { it('mock validationOk返回true', () => { const { validationOk, enableConfirmButton, enableBtn } = useFormData({ name: '', email: '' }); // 替换validationOk的实现 vi.spyOn({ validationOk }, 'validationOk').mockReturnValue(true); expect(validationOk()).toBe(true); enableConfirmButton(); expect(enableBtn.value).toBe(true); }); it('mock validationOk返回false', () => { const { validationOk, enableConfirmButton, enableBtn } = useFormData({ name: 'aa', email: 'ss@ss.com' }); vi.spyOn({ validationOk }, 'validationOk').mockReturnValue(false); expect(validationOk()).toBe(false); enableConfirmButton(); expect(enableBtn.value).toBe(false); }); });
方法3:通过vi.mock重写useFormData返回值(不修改原代码)
如果不想改动原函数代码,可通过vi.mock包装useFormData,替换其返回的validationOk:
import { useFormData } from '../../composables/useFormData'; import { describe, expect, it, vi } from 'vitest'; vi.mock('../../composables/useFormData.ts', async () => { const actual = await vi.importActual('../../composables/useFormData.ts'); return { ...actual, useFormData: vi.fn((user) => { const originalResult = actual.useFormData(user); // 替换validationOk为mock函数 originalResult.validationOk = vi.fn().mockReturnValue(true); return originalResult; }) }; }); describe('useFormData', () => { it('mock validationOk返回true', () => { const { validationOk } = useFormData({ name: '', email: '' }); expect(validationOk()).toBe(true); }); });
内容的提问来源于stack exchange,提问作者Saeem Osman
相关产品推荐
相关产品推荐

