如何在单元测试中Mock Nuxt 3的useAsyncData与useRuntimeConfig
问题描述
我需要为以下Nuxt 3组件编写单元测试:
<script setup lang="ts"> import { Content, fetchOneEntry, isPreviewing } from '@builder.io/sdk-vue' import { useRoute } from 'vue-router' const apiKey = useRuntimeConfig().public.builderIOAPIKey as string const model = 'page' const route = useRoute() const { data: content } = await useAsyncData(`builderData-page-${route.path}`, () => fetchOneEntry({ model, apiKey, userAttributes: { urlPath: route.path }, })) </script> <template> <div id="page"> <div v-if="content || isPreviewing()" class="w-full flex items-center justify-center" > <h1 class="typography-display-3 d-md:typography-display-2 d-lg:typography-display-1"> {{ (content?.data?.title) || 'Unpublished' }} </h1> <Content :model="model" :content="content" :api-key="apiKey" /> </div> </div> </template>
当前编写的测试用例如下:
it('renders published content when content exists', async () => { (fetchOneEntry as any).mockResolvedValue({ data: { title: 'Test Title' } }); (isPreviewing as any).mockReturnValue(false) const wrapper = mount(LandingPage) await flushPromises() const heading = wrapper.find('h1') expect(heading.exists()).toBe(true) expect(heading.text()).toBe('Test Title') const contentComponent = wrapper.findComponent(Content) expect(contentComponent.exists()).toBe(true) expect(contentComponent.props()).toEqual({ model: 'page', content: { data: { title: 'Test Title' } }, apiKey: 'test-api-key', }) expect(fetchOneEntry).toHaveBeenCalledWith({ model: 'page', apiKey: 'test-api-key', userAttributes: { urlPath: '/test-path' }, }) })
但挂载wrapper时无HTML输出,原因是测试环境中useRuntimeConfig和useAsyncData不可用。尝试过通过globalThis、vi.mock及mockNuxtImport来Mock这两个组合式函数,但均未成功。请问正确的Mock方式是什么?
解决方案
核心思路
Nuxt 3的内置组合式函数必须用官方测试工具提供的mockNuxtImport模拟,同时要统一处理路由和Builder.io的依赖,确保异步逻辑在测试中正确触发。
1. 统一模拟所有依赖
在测试文件顶部添加以下模拟代码,覆盖所有需要的组合式函数和第三方依赖:
import { mockNuxtImport, mount } from '@nuxt/test-utils/runtime' import { flushPromises, ref } from '@vue/test-utils' import LandingPage from './LandingPage.vue' import { Content, fetchOneEntry, isPreviewing } from '@builder.io/sdk-vue' // 模拟useRuntimeConfig,返回预设的API密钥 mockNuxtImport('useRuntimeConfig', () => { return () => ({ public: { builderIOAPIKey: 'test-api-key' } }) }) // 模拟useAsyncData,返回包含ref包裹数据的对象,适配组件的异步逻辑 mockNuxtImport('useAsyncData', () => { return (_key: string, handler: () => Promise<any>) => { return { data: ref(handler()) } } }) // 模拟useRoute,返回预设的路由路径 mockNuxtImport('useRoute', () => { return () => ({ path: '/test-path' }) }) // 整体模拟Builder.io的SDK,避免单独mock的作用域问题 vi.mock('@builder.io/sdk-vue', () => ({ Content: vi.fn(), fetchOneEntry: vi.fn().mockResolvedValue({ data: { title: 'Test Title' } }), isPreviewing: vi.fn().mockReturnValue(false) }))
2. 调整测试用例
保留原有断言逻辑即可,无需额外修改:
it('renders published content when content exists', async () => { const wrapper = mount(LandingPage) await flushPromises() // 验证标题渲染 const heading = wrapper.find('h1') expect(heading.exists()).toBe(true) expect(heading.text()).toBe('Test Title') // 验证Content组件的props传递 const contentComponent = wrapper.findComponent(Content) expect(contentComponent.exists()).toBe(true) expect(contentComponent.props()).toEqual({ model: 'page', content: { data: { title: 'Test Title' } }, apiKey: 'test-api-key', }) // 验证fetchOneEntry的调用参数 expect(fetchOneEntry).toHaveBeenCalledWith({ model: 'page', apiKey: 'test-api-key', userAttributes: { urlPath: '/test-path' }, }) })
关键注意事项
- 必须使用
@nuxt/test-utils/runtime提供的mockNuxtImport模拟Nuxt专属组合式函数,这是官方推荐的可靠方式,比直接修改globalThis更适配Nuxt的模块机制。 - 模拟
useAsyncData时,返回的data必须是ref类型,确保组件能响应数据变化,触发模板渲染。 - 对第三方SDK(如Builder.io)使用
vi.mock整体模拟,避免单独mock函数时出现的作用域冲突问题。
内容的提问来源于stack exchange,提问作者Nessario
相关产品推荐
相关产品推荐

