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

无法使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 11:23:19