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

使用mountSuspended测试含useNuxtApp的Nuxt3组件时Vitest报错

Nuxt3布局组件Vitest测试疑难解答

问题背景

我有一个需要用Vitest测试的简单Nuxt3布局组件,实际组件用到了await useLazyAsyncData(),所以采用mountSuspended()而非mount(),现在已移除异步逻辑用于调试。

组件代码

<!-- File: layouts/mount-suspended.vue -->
<template>
  <div>
    <h1>{{ isHydrating }}</h1>
    <NuxtPage />
  </div>
</template>

<script setup>
const nuxtApp = useNuxtApp() // auto-imported
const isHydrating = ref(nuxtApp.isHydrating)
</script>

测试代码

// File: layouts/tests/mount-suspended.test.js

// @vitest-environment nuxt
import { mockNuxtImport, mountSuspended } from '@nuxt/test-utils/runtime'
import { useRoute, useRouter } from 'vue-router'
import mountSuspendedLayout from '@/layouts/mount-suspended'

const mockRoute = {
  fullPath: '/seeded-test-a/courses',
  path: '/seeded-test-a/courses',
  params: {
    slug: 'seeded-test-a',
  },
}

vi.mock('vue-router')

mockNuxtImport('useNuxtApp', async () => {
  const actualUseNuxtApp = await vi.importActual('#app')
  return () => {
     return {
      ...actualUseNuxtApp.useNuxtApp(),
      isHydrating: true,
      deferHydration: () => {
        return () => {
          retVal.isHydrating.value = false
        }
      },
      hooks: {
        hookOnce: vi.fn(() => () => {}),
      },
    }
  }
})

describe('mountSuspended experiment', () => {
  let wrapper

  beforeEach(async () => {
    vi.mocked(useRoute).mockReturnValue(mockRoute)

    wrapper = await mountSuspended(mountSuspendedLayout, {
      global: {
        stubs: ['NuxtPage'],
      },
    })
  })

  it('is a component', () => {
    expect(wrapper.vm).toBeTruthy()
  }, 20000)
})

遇到的问题

  • 不mockuseNuxtApp时,测试会在10秒后超时
  • mockuseNuxtApp后超时问题解决,但出现路由相关报错,排查发现Nuxt内部调用useRouter()返回undefined,尝试mock未成功
  • 困惑于测试简单的useNuxtApp()调用为何如此复杂,想知道是否有内置工具简化流程,当前方向是否正确

解决方案与调整建议

1. 避免过度mock useNuxtApp

你当前的核心问题是手动mockuseNuxtApp时覆盖了太多内部属性,导致路由依赖缺失。不需要完全重写useNuxtApp,只局部mock需要的属性即可:

mockNuxtImport('useNuxtApp', () => {
  return () => ({
    // 只自定义需要的属性
    isHydrating: true,
    // 保留Nuxt测试环境默认的其他核心属性
    ...vi.importActual('#app').useNuxtApp()
  })
})

2. 补全vue-router的mock

你已经mock了useRoute,但缺少对useRouter的mock,补充后即可解决路由报错:

beforeEach(async () => {
  vi.mocked(useRoute).mockReturnValue(mockRoute)
  // 新增useRouter的mock,按需添加需要的路由方法
  vi.mocked(useRouter).mockReturnValue({
    push: vi.fn(),
    replace: vi.fn(),
    go: vi.fn()
  })

  wrapper = await mountSuspended(mountSuspendedLayout, {
    global: {
      stubs: ['NuxtPage'],
    },
  })
})

3. 利用Nuxt测试环境的内置支持

Nuxt的Vitest环境已经内置了大部分核心API的模拟,包括useNuxtApp和路由工具。除非需要自定义属性,否则无需手动mock整个useNuxtApp,直接修改上下文属性更简单:

import { useNuxtApp } from '#app'

beforeEach(async () => {
  const nuxtApp = useNuxtApp()
  nuxtApp.isHydrating = true

  vi.mocked(useRoute).mockReturnValue(mockRoute)
  vi.mocked(useRouter).mockReturnValue({ push: vi.fn() })

  wrapper = await mountSuspended(mountSuspendedLayout, {
    global: {
      stubs: ['NuxtPage'],
    },
  })
})

方向调整总结

你的测试方向是正确的,但过度mock导致引入额外问题。Nuxt测试工具的设计初衷就是简化核心API的测试,尽量依赖内置模拟环境,只针对需要自定义的部分做局部mock即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 23:37:02