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

如何用Vitest模拟Nuxt中的useSanctumAuth组合式函数?

解决Nuxt中间件测试中useNuxtApp未Mock的错误

问题场景

在对接Laravel API的Nuxt.js项目中,使用nuxt-auth-sanctum对接Laravel Fortify,编写了检查用户邮箱是否验证的全局中间件,并尝试用Vitest做TDD测试。

中间件代码

import { defineNuxtRouteMiddleware, navigateTo } from 'nuxt/app'
import type { User } from '~/models/user'
import { useSanctumAuth } from '#imports' // 因VS Code自动导入识别问题,手动从#imports导入

export default defineNuxtRouteMiddleware(async (to, from) => {
  const { user } = useSanctumAuth<User>()

  // 排除不需要验证的路由
  const excludedRoutes = ['/auth/login']
  if (excludedRoutes.includes(to.path)) {
    return
  }

  // 未验证邮箱则跳转至验证页面
  if (user.value && !user.value.email_verified_at) {
    return navigateTo('/auth/verify-email')
  }
})

测试代码及报错

测试时尝试Mock useSanctumAuth,但运行测试时报错:

[vitest] No "useNuxtApp" export is defined on the "nuxt/app" mock. Did you forget to return it from "vi.mock"?

测试代码:

import { describe, it, expect, vi, beforeEach } from 'vitest'
import checkEmailVerified from '@/middleware/check-email-verified.global'
import type { RouteLocationNormalized } from 'vue-router'
import { navigateTo } from 'nuxt/app'

vi.mock('nuxt/app', () => ({
  navigateTo: vi.fn(),
  defineNuxtRouteMiddleware: vi.fn((middleware) => middleware),
}))

vi.mock('#imports', async () => ({
  useSanctumAuth: vi.fn(() => ({
    user: {
      value: {
        email_verified_at: null,
      },
    },
  })),
}))

describe('checkEmailVerified middleware', () => {
    beforeEach(() => {
        vi.clearAllMocks()
    })

    it('should allow access to excluded routes without redirection', async () => {
        const to: RouteLocationNormalized = {
          path: '/auth/login',
        } as RouteLocationNormalized

        const from: RouteLocationNormalized = {
          path: '/',
        } as RouteLocationNormalized

        await checkEmailVerified(to, from) // 报错触发行
        expect(navigateTo).not.toHaveBeenCalled()
   })
})

错误原因

#imports 是Nuxt提供的虚拟自动导入模块,Vitest对这类虚拟模块的Mock支持有限,导致你的Mock并未真正拦截useSanctumAuth的调用——实际执行了nuxt-auth-sanctum包中真实的useSanctumAuth函数,而该函数内部依赖useNuxtApp,但你Mock的nuxt/app中没有导出这个函数,因此触发错误。


解决方案

方案1:直接Mock nuxt-auth-sanctum 模块(推荐)

useSanctumAuth实际来自nuxt-auth-sanctum包,直接Mock这个模块就能彻底替换真实函数,避免虚拟模块的Mock问题:

import { describe, it, expect, vi, beforeEach } from 'vitest'
import checkEmailVerified from '@/middleware/check-email-verified.global'
import type { RouteLocationNormalized } from 'vue-router'
import { navigateTo } from 'nuxt/app'

vi.mock('nuxt/app', () => ({
  navigateTo: vi.fn(),
  defineNuxtRouteMiddleware: vi.fn((middleware) => middleware),
  // 可选:若后续有其他依赖,可添加useNuxtApp的基础Mock
  useNuxtApp: vi.fn(() => ({})),
}))

// 直接Mock nuxt-auth-sanctum模块,替换useSanctumAuth
vi.mock('nuxt-auth-sanctum', () => ({
  useSanctumAuth: vi.fn(() => ({
    user: {
      value: {
        email_verified_at: null,
      },
    },
  })),
}))

describe('checkEmailVerified middleware', () => {
    beforeEach(() => {
        vi.clearAllMocks()
    })

    it('should allow access to excluded routes without redirection', async () => {
        const to: RouteLocationNormalized = {
          path: '/auth/login',
        } as RouteLocationNormalized

        const from: RouteLocationNormalized = {
          path: '/',
        } as RouteLocationNormalized

        await checkEmailVerified(to, from)
        expect(navigateTo).not.toHaveBeenCalled()
   })
})

方案2:在nuxt/app的Mock中补充useNuxtApp

如果坚持使用#imports的Mock方式,可在nuxt/app的Mock中添加useNuxtApp的导出,满足真实函数的依赖:

vi.mock('nuxt/app', () => ({
  navigateTo: vi.fn(),
  defineNuxtRouteMiddleware: vi.fn((middleware) => middleware),
  // 添加useNuxtApp的Mock,可根据需求补充内部属性
  useNuxtApp: vi.fn(() => ({
    $sanctum: {} // 示例:添加sanctum相关属性
  })),
}))

额外优化建议

  1. 移除手动导入:去掉中间件中import { useSanctumAuth } from '#imports'的手动导入,Nuxt的自动导入机制会自动处理,避免测试时的虚拟模块Mock问题。
  2. 动态Mock返回值:测试不同场景时,可动态修改useSanctumAuth的返回值,覆盖已验证用户、未登录用户等情况:
it('should redirect unverified user to verify email page', async () => {
  // 获取Mock的useSanctumAuth函数
  const useSanctumAuth = vi.mocked(await import('nuxt-auth-sanctum').then(m => m.useSanctumAuth))
  // 修改返回值为未验证用户
  useSanctumAuth.mockReturnValue({
    user: {
      value: {
        email_verified_at: null,
      },
    },
  })

  const to: RouteLocationNormalized = {
    path: '/dashboard',
  } as RouteLocationNormalized

  await checkEmailVerified(to, {} as RouteLocationNormalized)
  expect(navigateTo).toHaveBeenCalledWith('/auth/verify-email')
})

内容的提问来源于stack exchange,提问作者dust_bg

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 10:09:58