Nuxt3中如何测试含表单、useFetch与Pinia的SSR页面?
Nuxt3 SSR登录页面测试问题
项目代码(pages/login.vue)
<template> <form @submit.prevent="login" > <input name="email" v-model="form.email" /> <input name="password" v-model="form.password" /> <button>login</button> </form> </template> <script setup> import { useAuth } from '~/store/auth.js'; const auth = useAuth(); const form = ref({email: '', password: ''}); const router = useRouter(); const login = async () => { useFetch('/api/auth/tokens', { method: 'POST', body: form.value }) .then(({ data }) => { const { token, user } = data.value; auth.setUser(user); auth.setToken(token); router.push('/profile'); }) } </script>
尝试的测试方式及问题
1. SPA模式测试(@vue/test-utils mount)
使用mount方法时出现document is not defined错误,且无法测试useFetch回调中的响应处理和页面跳转逻辑:
import { describe, test, expect } from 'vitest'; import { mount } from '@vue/test-utils'; import LoginPage from '~/pages/login.vue'; describe('login page', async () => { test('store tokens', async () => { const wrapper = mount(LoginPage); const email = 'test@example.com'; const password = 'password'; await wrapper.find('[name="email"]').setValue(email); await wrapper.find('[name="password"]').setValue(password); await wrapper.find('form').trigger('submit.prevent'); expect(wrapper.emitted('submit')[0][0]).toStrictEqual({email,password}); // 无法测试then()中处理响应和跳转的代码 }); });
2. Nuxt官方文档方式(@nuxt/test-utils + JSDOM)
通过setup和$fetch得到的是JSDOM文档实例,无法像Vue组件一样操作元素:
import { describe, test, expect } from 'vitest'; import { setup, $fetch } from '@nuxt/test-utils'; import { JSDOM } from 'jsdom'; describe('login page', async () => { await setup(); test('store tokens', async () => { const wrapper = (new JSDOM(await $fetch('/login'))).window.document; const email = 'test@example.com'; const password = 'password'; await wrapper.find('[name="email"]').setValue(email); await wrapper.find('[name="password"]').setValue(password); await wrapper.find('form').trigger('submit.prevent'); expect($fetch).toHaveBeenCalledWith('/api/auth/tokens', { method: 'POST', body: {email,password} }); }); });
疑问
- 如何测试该页面的用户输入事件?
- 如何测试
useFetch回调中处理Pinia数据的逻辑?
解决方案
1. 测试用户输入事件
使用@nuxt/test-utils提供的mount方法(而非@vue/test-utils原生mount),它会自动配置Nuxt运行环境,解决document is not defined问题,同时支持Vue组件式的元素操作:
import { describe, test, expect } from 'vitest'; import { mount } from '@nuxt/test-utils/runtime'; import LoginPage from '~/pages/login.vue'; describe('Login Page - Input Handling', () => { test('captures user input correctly', async () => { const wrapper = await mount(LoginPage); const emailInput = wrapper.find('[name="email"]'); const passwordInput = wrapper.find('[name="password"]'); await emailInput.setValue('test@example.com'); await passwordInput.setValue('password'); expect(wrapper.vm.form.email).toBe('test@example.com'); expect(wrapper.vm.form.password).toBe('password'); }); });
核心要点:
- 依赖
@nuxt/test-utils/runtime的mount,它会初始化Nuxt上下文(包含Pinia、路由等) - 通过
wrapper.vm直接访问组件实例的form数据,验证双向绑定是否生效
2. 测试useFetch回调与Pinia逻辑
需要模拟useFetch的返回值,同时监听Pinia store方法和路由行为,验证回调逻辑是否正确执行:
import { describe, test, expect, vi } from 'vitest'; import { mount } from '@nuxt/test-utils/runtime'; import LoginPage from '~/pages/login.vue'; import { useAuth } from '~/store/auth.js'; import { ref } from 'vue'; // Mock useFetch 返回预设的成功响应 vi.mock('#app', () => ({ useFetch: vi.fn(() => Promise.resolve({ data: ref({ token: 'mock-token-123', user: { id: 1, email: 'test@example.com' } }) })) })); describe('Login Page - Auth Logic', () => { test('updates Pinia store and navigates on successful login', async () => { const wrapper = await mount(LoginPage); const authStore = useAuth(); // 监听Pinia方法调用 const mockSetUser = vi.spyOn(authStore, 'setUser'); const mockSetToken = vi.spyOn(authStore, 'setToken'); // 监听路由跳转 const router = useRouter(); const mockPush = vi.spyOn(router, 'push'); // 先设置输入值 await wrapper.find('[name="email"]').setValue('test@example.com'); await wrapper.find('[name="password"]').setValue('password'); // 触发表单提交 await wrapper.find('form').trigger('submit.prevent'); // 验证useFetch调用参数 expect(useFetch).toHaveBeenCalledWith('/api/auth/tokens', { method: 'POST', body: { email: 'test@example.com', password: 'password' } }); // 验证Pinia方法是否被正确调用 expect(mockSetUser).toHaveBeenCalledWith({ id: 1, email: 'test@example.com' }); expect(mockSetToken).toHaveBeenCalledWith('mock-token-123'); // 验证路由跳转是否触发 expect(mockPush).toHaveBeenCalledWith('/profile'); }); });
核心要点:
- 用
vi.mock模拟useFetch接口,返回预设的响应数据,避免依赖真实后端 - 通过
vi.spyOn监听Pinia的setUser/setToken方法、路由的push方法,验证逻辑触发情况 - 若需测试错误场景,只需修改
useFetch的模拟返回值(比如返回reject状态)
内容的提问来源于stack exchange,提问作者DengSihan
相关产品推荐
相关产品推荐

