Vue3组件测试:Vitest的vi.mock模拟登录错误未触发catch块
Vue3+TypeScript+Vitest 模拟登录错误无法触发catch块问题
问题描述
在为Vue3+TypeScript组件编写Vitest测试用例时,尝试模拟登录方法抛出错误以触发handleLogin中的catch块,但无论使用mockRejectedValue还是直接throw错误,都无法进入catch分支。使用mockResolveValue的成功场景测试正常。
测试代码(login.spec.ts)
import { describe, it, expect, vi, beforeAll } from 'vitest' import { mount } from '@vue/test-utils' import loginVue from '../app/auth/login.page.vue' import { createApp } from 'vue' import { createPinia } from 'pinia' import { router } from '@/plugins/vue-router' beforeAll(() => { const app = createApp({}) app.use(createPinia()) }) const mountLogin = () => { const wrapper = mount(loginVue, { global: { plugins: [router], stubs: ['Icon', 'RouterLink'] }, attachTo: document.body }) return { wrapper, router } } describe('Login Page', () => { it('Failed Login', async () => { vi.mock('@/stores/newAuth', () => ({ useNewAuthStore: () => ({ login: vi.fn().mockRejectedValue(new Error('Invalid')) }) })) const { wrapper } = mountLogin() const form = wrapper.find('form') await form.trigger('submit') await wrapper.vm.$nextTick() // console.log(wrapper.html()) expect(wrapper.findComponent({ name: 'ErrorNotif' }).exists()).toBe(true) }) })
组件中的handleLogin函数(login.page.vue)
const handleLogin = async () => { try { loadingStore.showLoading() await auth.login(loginData.value) router.push('/') loadingStore.hideLoading() } catch (error) { console.log(error) errorLogin.value = true loadingStore.hideLoading() } }
解决方案
问题核心在于**vi.mock的执行时机**和Pinia store的导入逻辑:Vitest的vi.mock会被自动提升到模块顶部执行,而组件在导入时已获取原始useNewAuthStore实例,导致后续mock无法覆盖。按以下步骤修改:
1. 将vi.mock移到模块顶部
确保组件导入前完成mock,避免原始store实例被提前加载:
import { describe, it, expect, vi, beforeAll } from 'vitest' import { mount } from '@vue/test-utils' import loginVue from '../app/auth/login.page.vue' import { createApp } from 'vue' import { createPinia } from 'pinia' import { router } from '@/plugins/vue-router' // 移至模块顶部,确保组件导入前完成mock vi.mock('@/stores/newAuth', () => ({ useNewAuthStore: vi.fn(() => ({ login: vi.fn().mockRejectedValue(new Error('Invalid')) })) })) // 其余代码保持不变
2. 支持多测试用例的动态mock
如果需要在不同测试用例中切换mock行为,先在模块顶部定义基础mock,再在测试用例内部修改实现:
// 模块顶部定义基础mock vi.mock('@/stores/newAuth', () => ({ useNewAuthStore: vi.fn(() => ({ login: vi.fn() // 留空,后续在测试用例中动态修改 })) })) // 导入mock的store函数(需先mock才能导入) import { useNewAuthStore } from '@/stores/newAuth' describe('Login Page', () => { it('Failed Login', async () => { // 获取mock的login方法,设置为拒绝状态 const mockLogin = vi.mocked(useNewAuthStore()).login mockLogin.mockRejectedValue(new Error('Invalid')) const { wrapper } = mountLogin() const form = wrapper.find('form') await form.trigger('submit') await wrapper.vm.$nextTick() expect(wrapper.findComponent({ name: 'ErrorNotif' }).exists()).toBe(true) }) it('Successful Login', async () => { const mockLogin = vi.mocked(useNewAuthStore()).login mockLogin.mockResolvedValue(true) // 成功场景测试逻辑 }) })
3. 修复Pinia初始化逻辑
原beforeAll中创建的App未关联到测试组件的挂载上下文,建议在挂载组件时直接传入Pinia实例:
const mountLogin = () => { const pinia = createPinia() // 每次挂载创建新的Pinia实例,避免测试污染 const wrapper = mount(loginVue, { global: { plugins: [pinia, router], // 传入Pinia到挂载上下文 stubs: ['Icon', 'RouterLink'] }, attachTo: document.body }) return { wrapper, router, pinia } }
修改完成后,mock的login方法拒绝时会正确触发handleLogin的catch块,进而显示错误提示组件。
内容的提问来源于stack exchange,提问作者irwnd2
相关产品推荐
相关产品推荐

