首页
/ Nuxt `test/` 目录实战:unit / nuxt / e2e 三层测试组织与 @nuxt/test-utils 配置详解

Nuxt `test/` 目录实战:unit / nuxt / e2e 三层测试组织与 @nuxt/test-utils 配置详解

2026-09-04 22:15:50作者:吴年前Myrtle

本篇围绕 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-domjsdom 之间二选一;
  • 运行器可选 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/e2esetup() 在外部拉起来一个真实的构建/服务进程,而不是在 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.tsresolveLayerPaths 生成的 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/(含 universaldevinsensitivesensitive 等子目录)加上一批 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.compatibilityVersionrouter.options.sensitive);
  • bundleno-jiti 等特殊项目单独声明,配更长的 testTimeout
  • 一个细节:仓库本地对 defineVitestProject 包了一层,默认关闭 experimental.nitroViteEnvironmentvitest.config.ts 中有注释 “TODO: fix upstream in nuxt/test-utils”)。

2. e2e 测试交给 Playwrightplaywright.config.tstestDir 指向 ./test/e2e,通过 @nuxt/test-utils/playwrightConfigOptions 提供全局 Nuxt 配置,并按 builder(vite / rspack / webpack)× 环境(dev / built)展开出六个项目,用 dependencies: ['setup fixtures'] 统一复用 fixture 构建。测试文件里则通过 test/e2e/test-utils.ts 扩展出 fetchgoto(自动等待 hydration)等 fixture,以及 toBeWithPollingtoHaveNoErrorsOrWarnings 等自定义 matcher——这是“把 e2e 与 Nuxt 运行时测试严格分目录”这一建议在超大型项目里的具体落地。

3. package.json scripts 按项目粒度切分入口(见 package.jsonscripts):

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-utilsmount),支持 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 方法。

两类注意事项:

  1. 全局状态:Nuxt 环境测试启动前会初始化一个全局 Nuxt 应用(会执行 app.vue 与插件),务必不要污染全局状态(或测完重置);
  2. 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)/ e2esetup() + Playwright,普通 node 项目)三层拆分,是用 Vitest projects 控制环境的稳定方案;
  • 只有 test/nuxt/tests/nuxt/ 默认进入 Nuxt 应用 TypeScript 上下文(见 packages/kit/src/template.ts),这既是自动导入类型可用的来源,也是把测试按此组织的原因;
  • 更多 setup 细节、helper API 与 Playwright 集成可继续阅读 Testing 指南packages/test-utils 中的实现与测试。
登录后查看全文
热门项目推荐
相关项目推荐