Nuxt `test/` 目录实战:unit / nuxt / e2e 三层测试组织与 @nuxt/test-utils 配置详解
本篇围绕 Nuxt 项目推荐的 test/ 测试目录展开:说明它为什么不会被 Nuxt 自动扫描、unit / nuxt / e2e 三类测试的划分原则,以及如何用 @nuxt/test-utils + Vitest projects 完成可复制的完整配置。读完你可以直接在项目里搭建出分环境的测试体系,并理解 Nuxt 自身仓库是如何用同一套结构运行上千个测试的。
test/ 目录的定位:Nuxt 不扫描它,规则由你定
官方文档对 test/ 的定义很明确:它是应用测试的推荐存放位置,但 Nuxt 不会像扫描 app/ 或 server/ 那样去扫描 test/——测试运行器(runner)和目录布局都由你自己选择,官方推荐的做法是搭配 @nuxt/test-utils 使用。
这一点在源码层面可以得到印证:Nuxt 生成应用 TypeScript 上下文时只显式纳入了两个测试目录,见 packages/kit/src/template.ts:
nuxt: [
join(relativeSrcDir, '**/*'),
join(relativeModulesDir, `*/runtime/**/*`),
join(relativeRootDir, `test/nuxt/**/*`),
join(relativeRootDir, `tests/nuxt/**/*`),
...
]
也就是说,test/ 目录对 Nuxt 构建器是完全“透明”的(不会进入 bundle、不参与组件自动注册),只有其中的 test/nuxt/(以及兼容的 tests/nuxt/)子目录会被加入 Nuxt 应用的 TypeScript 上下文。这个设计正是后文“目录划分原则”的底层原因。
推荐的三层目录结构
官方推荐按“测试运行环境”划分目录:
test/
├── e2e/ # 针对运行中应用的端到端测试
├── nuxt/ # 需要 Nuxt 运行时环境的测试
└── unit/ # 不依赖 Nuxt 运行时的快速 Node 测试
| 子目录 | 运行环境 | 典型场景 |
|---|---|---|
test/unit/ |
纯 Node 环境,速度快 | 工具函数、纯逻辑,不应依赖自动导入和 composables |
test/nuxt/ |
Nuxt 运行时(Vitest environment: 'nuxt') |
组件挂载(mountSuspended)、composable 测试、自动导入 mock |
test/e2e/ |
真实构建 + 启动服务进程,必要时拉起浏览器 | 页面请求($fetch)、Playwright 浏览器交互、SSR 输出验证 |
官方提醒:你可以自由选择任意测试结构,但把 Nuxt 运行时环境与 e2e 测试分开,对测试稳定性很重要(两者对进程、端口、浏览器资源的要求完全不同)。
用 @nuxt/test-utils 搭建三层测试体系
1. 安装依赖
@nuxt/test-utils 将 DOM 环境和测试运行器拆成了可选 peer dependencies,你可以自行选择组合(官方默认推荐 Vitest):
# npm
npm i --save-dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
# pnpm
pnpm add -D @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
- DOM 环境可在
happy-dom与jsdom之间二选一; - 运行器可选
vitest/cucumber/jest/playwright; playwright-core仅在你想用内置浏览器测试工具(而非@playwright/test)时才需要。
2. 可选:把 Vitest 集成进 Nuxt DevTools
在 nuxt.config 中加入模块(非必须),开发期就能在 DevTools 中运行单测:
export default defineNuxtConfig({
modules: [
'@nuxt/test-utils/module',
],
})
3. 配置 Vitest projects:核心是“按目录绑定环境”
创建 vitest.config.ts,用 Vitest 的 projects 机制把三个子目录分别绑定到对应环境:
import { defineConfig } from 'vitest/config'
import { defineVitestProject } from '@nuxt/test-utils/config'
export default defineConfig({
test: {
projects: [
{
test: {
name: 'unit',
include: ['test/unit/*.{test,spec}.ts'],
environment: 'node',
},
},
{
test: {
name: 'e2e',
include: ['test/e2e/*.{test,spec}.ts'],
environment: 'node',
},
},
await defineVitestProject({
test: {
name: 'nuxt',
include: ['test/nuxt/*.{test,spec}.ts'],
environment: 'nuxt',
},
}),
],
},
})
两个关键点:
defineVitestProject只用于 Nuxt 环境测试。e2e 项目就是普通的environment: 'node'项目——因为 e2e 通过@nuxt/test-utils/e2e的setup()在外部拉起来一个真实的构建/服务进程,而不是在 Vitest worker 内初始化 Nuxt;- 在 vitest 配置中导入
@nuxt/test-utils时,需要在package.json指定"type": "module",或把配置文件改名为vitest.config.m{ts,js}。
4. 简化配置与 .env.test
如果希望默认所有测试都跑在 Nuxt 环境里,可以用 defineVitestConfig 的简单形态,并对个别文件用 // @vitest-environment node 注释退出 Nuxt 环境(官方不推荐这种混合环境,因为 Nuxt Vite 插件会运行但 nuxtApp 未初始化,容易出现难以调试的错误):
import { defineVitestConfig } from '@nuxt/test-utils/config'
import { fileURLToPath } from 'node:url'
export default defineVitestConfig({
test: {
environment: 'nuxt',
// environmentOptions: {
// nuxt: {
// rootDir: fileURLToPath(new URL('./playground', import.meta.url)),
// domEnvironment: 'happy-dom', // 'happy-dom'(默认)或 'jsdom'
// overrides: { /* 其他 Nuxt 配置覆盖 */ },
// }
// }
},
})
另外,可以用 .env.test 文件为测试注入环境变量。
5. 按项目运行测试
# 运行全部测试
npx vitest
# 只跑 unit / nuxt 项目
npx vitest --project unit
npx vitest --project nuxt
# watch 模式
npx vitest --watch
TypeScript 上下文:为什么测试要放在 test/nuxt/
官方文档的 TypeScript Support in Tests 一节解释了这套目录约定最大的收益:test/nuxt/ 与 tests/nuxt/ 下的测试文件默认包含在 Nuxt 应用的 TypeScript 上下文中,因此编辑器能识别 ~/、@/、#imports 等 Nuxt 别名,也能感知自动导入(auto-imports)——你在测试里直接写 useFetch(...) 不会有类型报错。
这条规则来自 packages/kit/src/template.ts 中 resolveLayerPaths 生成的 tsconfig include 列表(test/nuxt/**/*、tests/nuxt/**/*),并有专门的快照测试固化该行为,见 packages/kit/test/generate-types.spec.ts。
如果你的 Nuxt 环境测试放在其他目录(比如 test/other-nuxt-context/),需要手动把它们加入应用 tsconfig:
// nuxt.config.ts
export default defineNuxtConfig({
typescript: {
tsConfig: {
include: [
// 该路径相对于生成的 .nuxt/tsconfig.json
'../test/other-nuxt-context/**/*',
],
},
},
})
注意:unit 测试不应依赖自动导入、composables 等 Nuxt 运行时特性;只有在测试确实 import 源码文件(如 ~/utils/helpers)时,才为它们补充路径别名支持。
实战参照:Nuxt 仓库自身的 test/ 目录
Nuxt 官方文档明说 @nuxt/test-utils “驱动着 Nuxt 自身的测试与整个模块生态的测试”。当前仓库就是一份三层结构的完整示例,结构上正是 test/e2e/、test/nuxt/(含 universal、dev、insensitive、sensitive 等子目录)加上一批 test/*.test.ts 场景测试。可以从三处看到它的组织方式:
1. 顶层 vitest.config.ts 声明了完整的 projects 矩阵:
unit项目:include: ['packages/**/*.{test,spec}.ts'],即各包的纯单元测试,并大量使用 resolve alias(如#app、#build/nuxt.config.mjs)指向 test/mocks/ 中的 mock 模块;nuxt/nuxt-legacy/nuxt-universal/nuxt-dev等 Nuxt 环境项目:全部通过defineVitestProject创建,environment: 'nuxt',并通过environmentOptions.nuxt.overrides注入不同的 Nuxt 配置(如future.compatibilityVersion、router.options.sensitive);bundle、no-jiti等特殊项目单独声明,配更长的testTimeout;- 一个细节:仓库本地对
defineVitestProject包了一层,默认关闭experimental.nitroViteEnvironment(vitest.config.ts 中有注释 “TODO: fix upstream in nuxt/test-utils”)。
2. e2e 测试交给 Playwright:playwright.config.ts 将 testDir 指向 ./test/e2e,通过 @nuxt/test-utils/playwright 的 ConfigOptions 提供全局 Nuxt 配置,并按 builder(vite / rspack / webpack)× 环境(dev / built)展开出六个项目,用 dependencies: ['setup fixtures'] 统一复用 fixture 构建。测试文件里则通过 test/e2e/test-utils.ts 扩展出 fetch、goto(自动等待 hydration)等 fixture,以及 toBeWithPolling、toHaveNoErrorsOrWarnings 等自定义 matcher——这是“把 e2e 与 Nuxt 运行时测试严格分目录”这一建议在超大型项目里的具体落地。
3. package.json scripts 按项目粒度切分入口(见 package.json 的 scripts):
pnpm test:unit # vitest run --project unit
pnpm test:runtime # vitest run --project nuxt --project nuxt-universal --project nuxt-legacy --project nuxt-dev --project nuxt-insensitive --project nuxt-sensitive
pnpm test:fixtures # vitest run --project 'fixtures:*'(dev/built × 各 builder 组合)
pnpm test:e2e # playwright test
pnpm test:e2e:dev # playwright test --project 'e2e-vite-dev'
可以看到,--project 参数与 vitest.config.ts 中声明的项目名一一对应,这也是官方文档推荐的分层运行方式在 monorepo 场景下的直接体现。
编写 test/nuxt/ 测试时的常用运行时 API
配合目录约定,@nuxt/test-utils/runtime 提供了几类核心 helper(详见 Testing 文档):
mountSuspended(component, options):在 Nuxt 环境中挂载组件(内部包装@vue/test-utils的mount),支持route选项指定初始路由;renderSuspended(component, options):基于@testing-library/vue的渲染版本,组件会渲染进<div id="test-wrapper">;mockNuxtImport(name, factory):mock Nuxt 自动导入(如useState)。它会被转换为vi.mock并被提升(hoist),因此每个被 mock 的导入每个测试文件只能调用一次;需要跨测试切换实现时用vi.hoisted暴露 mock 函数;mockComponent(nameOrPath, factory):mock 具名组件或按路径 mock;registerEndpoint(path, handler):向测试用 Nitro 注册返回 mock 数据的端点,第二个参数可以是{ method, handler, once }对象以匹配特定 HTTP 方法。
两类注意事项:
- 全局状态:Nuxt 环境测试启动前会初始化一个全局 Nuxt 应用(会执行
app.vue与插件),务必不要污染全局状态(或测完重置); - e2e 与 runtime 不能同文件:
@nuxt/test-utils/runtime与@nuxt/test-utils/e2e需要不同的测试环境,混用会冲突。拆成两个文件即可:runtime 测试命名为*.nuxt.spec.ts(或用// @vitest-environment nuxt注释声明),e2e 测试在describe内调用await setup({...})。
小结
test/是 Nuxt 测试的推荐位置,构建器不扫描它,目录布局与 runner 完全由你决定;- 按
unit(纯 Node)/nuxt(Nuxt 运行时,defineVitestProject)/e2e(setup()+ Playwright,普通 node 项目)三层拆分,是用 Vitest projects 控制环境的稳定方案; - 只有
test/nuxt/与tests/nuxt/默认进入 Nuxt 应用 TypeScript 上下文(见 packages/kit/src/template.ts),这既是自动导入类型可用的来源,也是把测试按此组织的原因; - 更多 setup 细节、helper API 与 Playwright 集成可继续阅读 Testing 指南 与 packages/test-utils 中的实现与测试。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00