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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 17:23:20