测试工具

如何测试你的 Nuxt 应用。

Maschinelle Übersetzung ·0 % geprüft

如果你是模块作者,可以在模块作者指南中找到更具体的信息。

安装

为了让你管理其他测试依赖,@nuxt/test-utils 附带了各种可选的 peer 依赖。例如:

  • 你可以在运行时 Nuxt 环境中选择 happy-dom 或 jsdom
  • 你可以为端到端测试运行器选择 vitest、cucumber、jest 或 playwright
  • 仅当你希望使用内置的浏览器测试工具(且不使用 @playwright/test 作为测试运行器)时,才需要 playwright-core
npm i --save-dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core

测试环境

@nuxt/test-utils 为两种不同的测试环境提供辅助函数:

  • Nuxt 环境:测试在 Nuxt 应用中运行,使用 happy-dom 或 jsdom 环境,并可访问自动导入、插件和注入。@nuxt/test-utils/runtime 中的辅助函数(例如 mountSuspended 和 mockNuxtImport)需要此环境,目前仅支持 Vitest。
  • Node 环境:测试在纯 Node 中运行。使用 @nuxt/test-utils/e2e 的端到端测试需要此环境,因为它们通过 HTTP 或浏览器从外部测试 Nuxt 应用。不需要 Nuxt 的单元测试也可以在此运行。

一个测试文件只能在其中一种环境中运行,因此不能在同一文件中使用 @nuxt/test-utils/runtime 和 @nuxt/test-utils/e2e。使用 Vitest 时,可以使用项目确保每个测试文件运行在正确的环境中,如下方设置所示。

单元测试

我们目前为需要 Nuxt 运行时环境的代码提供了一个单元测试环境。当前「仅支持 vitest」(欢迎贡献以添加其他运行时)。

设置

  1. (可选)在你的 nuxt.config 文件中添加 @nuxt/test-utils/module。它会向 Nuxt DevTools 添加一个 Vitest 集成,支持在开发时运行你的单元测试。

    export default defineNuxtConfig({
      modules: [
        '@nuxt/test-utils/module',
      ],
    })
  2. 创建一个包含以下内容的 vitest.config.ts:

    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 环境测试。端到端测试应配置为常规的 test.environment: 'node' 项目。
  3. 如果你的 Nuxt 环境测试位于 test/nuxt/ 之外,请参阅测试中的 TypeScript 支持,将其添加到 TypeScript 上下文中。

在你的 vitest 配置中导入 @nuxt/test-utils 时,需要在你的 package.json 中指定 "type": "module",或者适当地重命名你的 vitest 配置文件。

即,vitest.config.m{ts,js}。
可以使用 .env.test 文件为测试设置环境变量。

Nuxt 环境选项

你可以通过 test.environmentOptions.nuxt 向 defineVitestProject 项目传入 Nuxt 特定的选项:

import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vitest/config'
import { defineVitestProject } from '@nuxt/test-utils/config'

export default defineConfig({
  test: {
    projects: [
      await defineVitestProject({
        test: {
          name: 'nuxt',
          include: ['test/nuxt/*.{test,spec}.ts'],
          environmentOptions: {
            nuxt: {
              rootDir: fileURLToPath(new URL('./playground', import.meta.url)),
              domEnvironment: 'happy-dom', // 'happy-dom'(默认)或 'jsdom'
              overrides: {
                // 需要传入的其他 Nuxt 配置
              },
            },
          },
        },
      }),
    ],
  },
})

组织你的测试

使用基于项目的设置,你可能会如下组织你的测试:

Verzeichnisstruktur
test/
├── e2e/
│   └── ssr.test.ts
├── nuxt/
│   ├── components.test.ts
│   └── composables.test.ts
├── unit/
│   └── utils.test.ts

如果希望将测试与源文件放在一起,也可以使用 *.unit.spec.ts、*.nuxt.spec.ts 和 *.e2e.spec.ts 等后缀,并配置对应的 include glob(例如 include: ['**/*.nuxt.{test,spec}.ts'])。完整配置请参阅共置示例。

当然你可以选择任意测试结构,但每个测试文件都应只匹配一个项目。尤其是将 Nuxt 环境测试与端到端测试分开对测试稳定性很重要。

请使用项目的 include glob 为测试文件选择环境,而不要使用 // @vitest-environment 注释。该注释会改变环境,却不会添加或移除 defineVitestProject 配置的 Nuxt Vite 插件。

测试中的 TypeScript 支持

默认情况下,test/nuxt/ 和 tests/nuxt/ 目录中的测试文件会被包含在 Nuxt 应用的 TypeScript 上下文中。这意味着它们可以识别 Nuxt 别名(如 ~/、@/、#imports),并且 TypeScript 会识别在 Nuxt 应用中生效的自动导入。

这符合推荐的结构,只有需要 Nuxt 运行时环境的测试才放在这些目录中。其他目录(如 test/unit/)中的单元测试可以根据需要手动添加。
添加其他测试目录

如果您在 Nuxt Vitest 环境中运行的其他目录中有测试,您可以通过将它们添加到配置中,将它们包含在 Nuxt 应用程序 TypeScript 上下文中:

nuxt.config.ts
export default defineNuxtConfig({
  typescript: {
    tsConfig: {
      include: [
        // 该路径相对于生成的 .nuxt/tsconfig.json
        '../test/other-nuxt-context/**/*',
      ],
    },
  },
})
单元测试不应依赖于 Nuxt 运行时功能,如自动导入或组合函数。只有当你的测试从源文件(例如 ~/utils/helpers)导入时,才添加 TypeScript 路径别名支持,而不是针对 Nuxt 特定功能。

运行测试

使用项目设置,你可以运行不同的测试套件:

# 运行所有测试
npx vitest

# 仅运行单元测试
npx vitest --project unit

# 仅运行 Nuxt 测试
npx vitest --project nuxt

# 仅运行端到端测试
npx vitest --project e2e

# 以监听模式运行测试
npx vitest --watch

当你在 Nuxt 环境中运行测试时,它们将运行在 happy-dom 或 jsdom 环境中。在测试运行之前,一个全局的 Nuxt 应用将被初始化(例如,会运行你在 app.vue 中定义的任何插件或代码)。

这意味着你在测试中应该特别注意不要去改变全局状态(或者如果需要改变,测试后请务必重置它)。

🎭 内置模拟

@nuxt/test-utils 为 DOM 环境提供了一些内置模拟。这些模拟仅在 Nuxt 环境中可用。

intersectionObserver

默认 true,创建一个不具功能的 IntersectionObserver API 占位类。

indexedDB

默认 false,使用 fake-indexeddb 创建一个功能完整的 IndexedDB API 模拟。

这些可以在 vitest.config.ts 文件的Nuxt 环境选项中配置:

import { defineConfig } from 'vitest/config'
import { defineVitestProject } from '@nuxt/test-utils/config'

export default defineConfig({
  test: {
    projects: [
      await defineVitestProject({
        test: {
          name: 'nuxt',
          include: ['test/nuxt/*.{test,spec}.ts'],
          environmentOptions: {
            nuxt: {
              mock: {
                intersectionObserver: true,
                indexedDb: true,
              },
            },
          },
        },
      }),
    ],
  },
})

🛠️ 帮助函数

@nuxt/test-utils 提供了许多帮助函数以便更方便地测试 Nuxt 应用。

mountSuspended

mountSuspended 允许你在 Nuxt 环境中挂载任意 Vue 组件,支持异步设置并能够访问来自 Nuxt 插件的注入。

在内部,mountSuspended 封装了来自 @vue/test-utils 的 mount,因此你可以查看 Vue Test Utils 文档 以了解可传入选项以及如何使用此工具。

例如:

// @noErrors
import type { Component } from 'vue'

declare module '#components' {
  export const SomeComponent: Component
}
// ---cut---
import { expect, it } from 'vitest'
import { mountSuspended } from '@nuxt/test-utils/runtime'
import { SomeComponent } from '#components'

it('可以挂载某个组件', async () => {
  const component = await mountSuspended(SomeComponent)
  expect(component.text()).toMatchInlineSnapshot(
    '"这是一个自动导入的组件"',
  )
})
import { expect, it } from 'vitest'
import { mountSuspended } from '@nuxt/test-utils/runtime'
import { SomeComponent } from '#components'
import App from '~/app.vue'

it('can also mount an app', async () => {
  const component = await mountSuspended(App, { route: '/test' })
  expect(component.html()).toMatchInlineSnapshot(`
      "<div>这是一个自动导入的组件</div>
      <div> 我是一个全局组件 </div>
      <div>/</div>
      <a href="/test"> 测试链接 </a>"
    `)
})

如果你需要监视组件设置状态,可以将 spy 选项设为 true,并通过 setupState 访问 setup 的返回值。

// @noErrors
import type { Component } from 'vue'

declare module '#components' {
  export const SomeComponent: Component
}
// ---cut---
import { expect, it, vi } from 'vitest'
import { mountSuspended } from '@nuxt/test-utils/runtime'
import { SomeComponent } from '#components'

it('can spy on setup state', async () => {
  const component = await mountSuspended(SomeComponent, { spy: true })
  vi.mocked(component.setupState.someMethod).mockImplementation(() => 'mocked')
})

选项对象接受 @vue/test-utils 的挂载选项以及以下属性:

  • route:初始路由,或设为 false 以跳过初始路由更改(默认值为 /)。
  • spy:启用对组件 setup 状态的监视(默认值为 false)。

返回对象包含 @vue/test-utils 的挂载结果以及以下属性:

  • setupState:组件 setup 的返回值。

renderSuspended

renderSuspended 允许你在 Nuxt 环境中使用 @testing-library/vue 来渲染任意 Vue 组件,支持异步设置并能访问来自 Nuxt 插件的注入。

该方法应与 Testing Library 的实用工具(例如 screen 和 fireEvent)一起使用。请在你的项目中安装 @testing-library/vue 以使用这些功能。

此外,Testing Library 还依赖测试全局变量来进行清理。你应在你的 Vitest 配置 中启用这些全局变量。

传入的组件将在一个 <div id="test-wrapper"></div> 内渲染。

示例:

// @noErrors
import type { Component } from 'vue'

declare module '#components' {
  export const SomeComponent: Component
}
// ---cut---
import { expect, it } from 'vitest'
import { renderSuspended } from '@nuxt/test-utils/runtime'
import { SomeComponent } from '#components'
import { screen } from '@testing-library/vue'

it('可以渲染某个组件', async () => {
  await renderSuspended(SomeComponent)
  expect(screen.getByText('这是一个自动导入的组件')).toBeDefined()
})
import { expect, it } from 'vitest'
import { renderSuspended } from '@nuxt/test-utils/runtime'
import App from '~/app.vue'

it('也可以渲染一个应用', async () => {
  const html = await renderSuspended(App, { route: '/test' })
  expect(html).toMatchInlineSnapshot(`
    "<div id="test-wrapper">
      <div>这是一个自动导入的组件</div>
      <div> 我是一个全局组件 </div>
      <div>首页</div><a href="/test"> 测试链接 </a>
    </div>"
  `)
})

选项对象接受 @testing-library/vue 的 render 选项以及以下属性:

  • route:初始路由,或设为 false 以跳过初始路由更改(默认值为 /)。
  • spy:启用对组件 setup 状态的监视(默认值为 false)。请参阅上方的 mountSuspended 示例。

返回对象包含 @testing-library/vue 的 render 结果以及以下属性:

  • setupState:组件 setup 的返回值。

mockNuxtImport

mockNuxtImport 允许你模拟 Nuxt 的自动导入功能。例如,要模拟 useState,你可以这样做:

import { mockNuxtImport } from '@nuxt/test-utils/runtime'

mockNuxtImport('useState', () => {
  return () => {
    return { value: 'mocked storage' }
  }
})

// 你的测试写在这里

你可以显式为 mock 进行类型标注以确保类型安全,并在模拟复杂功能时使用传递给工厂函数的原始实现。

import { mockNuxtImport } from '@nuxt/test-utils/runtime'

mockNuxtImport<typeof useState>('useState', (original) => {
  return (...args) => {
    return { ...original('some-key'), value: 'mocked state' }
  }
})

// 或者指定要模拟的目标
mockNuxtImport(useState, (original) => {
  return (...args) => {
    return { ...original('some-key'), value: 'mocked state' }
  }
})

// 你的测试写在这里
mockNuxtImport 在每个测试文件中每个被模拟的导入只能使用一次。它实际上是一个宏,会被转换为 vi.mock,而 vi.mock 会被提升,详见 Vitest 文档。

如果你需要在不同测试之间为 Nuxt 导入提供不同的实现,可以通过创建并暴露你的模拟(使用 vi.hoisted)来实现,然后在 mockNuxtImport 中使用这些模拟。这样你可以访问被模拟的导入,并在测试之间更改实现。注意在每个测试前后恢复模拟状态以撤销模拟的状态更改(参见 restore mocks)。

import { vi } from 'vitest'
import { mockNuxtImport } from '@nuxt/test-utils/runtime'

const { useStateMock } = vi.hoisted(() => {
  return {
    useStateMock: vi.fn(() => {
      return { value: 'mocked storage' }
    }),
  }
})

mockNuxtImport('useState', () => {
  return useStateMock
})

// 然后,在测试内部
useStateMock.mockImplementation(() => {
  return { value: '其他内容' }
})

如果你需要仅在测试内部模拟行为,也可以使用以下方法。

import { beforeEach, vi } from 'vitest'
import { mockNuxtImport } from '@nuxt/test-utils/runtime'

mockNuxtImport(useRoute, original => vi.fn(original))

beforeEach(() => {
  vi.resetAllMocks()
})

// 然后,在测试内部
const useRouteOriginal = vi.mocked(useRoute).getMockImplementation()!
vi.mocked(useRoute).mockImplementation(
  (...args) => ({ ...useRouteOriginal(...args), path: '/mocked' }),
)

mockComponent

mockComponent 允许你模拟 Nuxt 的组件。
第一个参数可以是 PascalCase 的组件名,或组件的相对路径。
第二个参数是返回被模拟组件的工厂函数。

例如,要模拟 MyComponent,你可以:

import { mockComponent } from '@nuxt/test-utils/runtime'

mockComponent('MyComponent', {
  props: {
    value: String,
  },
  setup (props) {
    // ...
  },
})

// 相对路径或别名也可
mockComponent('~/components/my-component.vue', () => {
  // 或者一个工厂函数
  return defineComponent({
    setup (props) {
      // ...
    },
  })
})

// 或者你可以使用 SFC 将其重定向到一个模拟组件
mockComponent('MyComponent', () => import('./MockComponent.vue'))

// 你的测试写在这里
注意:工厂函数由于会被提升,不能在其中引用本地变量。如果你需要访问 Vue API 或其他变量,需要在工厂函数中导入它们。
import { mockComponent } from '@nuxt/test-utils/runtime'

mockComponent('MyComponent', async () => {
  const { ref, h } = await import('vue')

  return defineComponent({
    setup (props) {
      const counter = ref(0)
      return () => h('div', null, counter.value)
    },
  })
})

registerEndpoint

registerEndpoint 允许你创建返回模拟数据的 Nitro 接口。当你想测试一个向 API 发起请求以显示数据的组件时,这非常有用。

第一个参数是接口名称(例如 /test/)。
第二个参数是一个返回模拟数据的工厂函数。

例如,要模拟 /test/ 接口,你可以:

import { registerEndpoint } from '@nuxt/test-utils/runtime'

registerEndpoint('/test/', () => ({
  test: 'test-field',
}))

默认情况下,请求将使用 GET 方法。你可以通过将第二个参数设置为对象(而不是函数)来使用其他方法。

import { registerEndpoint } from '@nuxt/test-utils/runtime'

registerEndpoint('/test/', {
  method: 'POST',
  handler: () => ({ test: 'test-field' }),
})

此对象接受以下属性:

  • handler:事件处理函数
  • method:(可选)匹配的 HTTP 方法(例如 'GET'、'POST')
  • once:(可选)如果为 true,处理程序只会用于第一个匹配的请求,然后自动移除
注意:如果你的组件中的请求指向外部 API,你可以使用 baseURL,然后使用 Nuxt 环境覆盖配置($test)将其置空,这样你的所有请求都会指向 Nitro 服务器。
  • handler:事件处理函数
  • method:(可选)匹配的 HTTP 方法(例如 'GET'、'POST')
  • once:(可选)如果为 true,处理程序只会用于第一个匹配的请求,然后自动移除
注意:如果你的组件中的请求指向外部 API,你可以使用 baseURL,然后使用 Nuxt 环境覆盖配置($test)将其置空,这样你的所有请求都会指向 Nitro 服务器。

与端到端测试的冲突

@nuxt/test-utils/runtime 和 @nuxt/test-utils/e2e 需要在不同的测试环境中运行,因此不能在同一个文件中同时使用。

如果你想同时使用 @nuxt/test-utils 的端到端和单元测试功能,请将测试拆分到不同的文件中,并确保每个文件只匹配一个项目:Nuxt 环境测试应匹配 defineVitestProject 项目,端到端测试则匹配常规的 environment: 'node' 项目。请参阅组织你的测试了解按文件夹或后缀组织的布局。

app.nuxt.spec.ts

import { mockNuxtImport } from '@nuxt/test-utils/runtime'

mockNuxtImport('useState', () => {
  return () => {
    return { value: 'mocked storage' }
  }
})

app.e2e.spec.ts

import { $fetch, setup } from '@nuxt/test-utils/e2e'

await setup({
  setupTimeout: 10000,
})

// ...

使用 @vue/test-utils

如果你更愿意单独使用 @vue/test-utils 在 Nuxt 中进行单元测试,且你仅测试不依赖 Nuxt composables、自动导入或上下文的组件,可以按以下步骤设置。

  1. 安装所需依赖

    npm i --save-dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
  2. 创建一个包含以下内容的 vitest.config.ts:

    import { defineConfig } from 'vitest/config'
    import vue from '@vitejs/plugin-vue'
    
    export default defineConfig({
      plugins: [vue()],
      test: {
        environment: 'happy-dom',
      },
    })
  3. 在你的 package.json 中添加一个测试脚本

    "scripts": {
      "build": "nuxt build",
      "dev": "nuxt dev",
      ...
      "test": "vitest"
    },
  4. 创建一个简单的 <HelloWorld> 组件 app/components/HelloWorld.vue,内容如下:

    <template>
      <p>你好,世界</p>
    </template>
  5. 为这个新建组件创建一个简单的单元测试 ~/components/HelloWorld.spec.ts

    import { describe, expect, it } from 'vitest'
    import { mount } from '@vue/test-utils'
    
    import HelloWorld from './HelloWorld.vue'
    
    describe('HelloWorld', () => {
      it('组件正确渲染你好,世界', () => {
        const wrapper = mount(HelloWorld)
        expect(wrapper.text()).toContain('你好,世界')
      })
    })
  6. 运行 vitest 命令

    npm run test

恭喜,你现在已准备好在 Nuxt 中使用 @vue/test-utils 进行单元测试!祝测试愉快!

Vitest 浏览器模式

@nuxt/test-utils 通过 @nuxt/test-utils/browser 提供了在 Vitest 浏览器模式中进行测试的帮助函数。

设置

  1. 安装 Vitest 浏览器包和浏览器提供程序:

    npm i --save-dev @vitest/browser-playwright
  2. 在你的 vitest.config.ts 中配置浏览器模式:

    // @errors: 2307
    // ---cut---
    import { defineConfig } from 'vitest/config'
    import { defineVitestProject } from '@nuxt/test-utils/config'
    import { playwright } from '@vitest/browser-playwright'
    
    export default defineConfig({
      test: {
        projects: [
          await defineVitestProject({
            test: {
              name: 'browser',
              include: ['test/browser/**/*.{test,spec}.ts'],
              browser: {
                enabled: true,
                provider: playwright(),
                instances: [{ browser: 'chromium' }],
              },
              // Wenn du `page.render` und die automatische Bereinigung aktivieren möchtest, füge diese Setup-Datei hinzu
              setupFiles: ['@nuxt/test-utils/browser'],
            },
          }),
        ],
      },
    })
    将 @nuxt/test-utils/browser 添加到 setupFiles 时,如果 TypeScript 没有自动识别 page.render 的类型,你可以通过 nuxt.config.ts 中的 typescript.tsConfig.compilerOptions.types 添加 @nuxt/test-utils/browser。
    如果你的 Vitest 浏览器模式测试位于 test/nuxt/ 之外(例如 test/browser/),请参阅添加其他测试目录,将它们添加到 TypeScript 上下文中。

使用

你可以使用 vitest/browser 中的 page.render:

// @noErrors
import type { Component } from 'vue'

declare module '#components' {
  export const MyCounter: Component
}
// ---cut---
import { expect, it } from 'vitest'
import { page } from 'vitest/browser'
// Wenn du die Setup-Datei hinzugefügt hast, ist der folgende Import nicht nötig
import '@nuxt/test-utils/browser'

import { MyCounter } from '#components'

it('counter button increments the count', async () => {
  const screen = await page.render(MyCounter)
  await screen.getByRole('button', { name: 'Increment' }).click()
  await expect.element(screen.getByText('Count: 1')).toBeVisible()
})

你也可以直接从 @nuxt/test-utils/browser 导入 render:

// @noErrors
import type { Component } from 'vue'

declare module '#components' {
  export const MyCounter: Component
}
// ---cut---
import { expect, it } from 'vitest'
import { render } from '@nuxt/test-utils/browser'
import { MyCounter } from '#components'

it('can render using the render helper', async () => {
  const screen = await render(MyCounter)
  await screen.getByRole('button', { name: 'Increment' }).click()
  await expect.element(screen.getByText('Count: 1')).toBeVisible()
})

选项对象接受 @vue/test-utils 的挂载选项(使用 container 而不是 attachTo)以及以下属性:

  • route:初始路由,或设为 false 以跳过初始路由更改(默认值为 /)。
  • spy:启用对组件 setup 状态的监视(默认值为 false)。请参阅上方的 mountSuspended 示例。
  • container:用于渲染的自定义 HTMLElement 容器(使用此选项代替 @vue/test-utils 的 attachTo)。
  • baseElement:自定义基础 HTMLElement(默认值为 document.body)。

返回对象包含以下属性:

  • container:组件渲染到其中的容器 HTMLElement。
  • baseElement:基础 HTMLElement(默认值为 document.body)。
  • locator:根元素 Locator。
  • setupState:组件 setup 的返回值;启用 spy 选项时为模拟值。
  • debug():将格式化后的 DOM 打印到控制台。
  • unmount():卸载组件。也会记录一个 nuxt.unmount 跟踪标记。
  • emitted():获取触发的事件。
  • rerender(props):使用新 props 重新渲染组件。也会记录一个 nuxt.rerender 跟踪标记。

端到端测试

对于端到端测试,我们支持 Vitest、Jest、Cucumber 和 Playwright 作为测试运行器。

设置

在每个使用 @nuxt/test-utils/e2e 辅助方法的 describe 块中,你需要在开始之前设置测试上下文。

import { describe, test } from 'vitest'
import { $fetch, setup } from '@nuxt/test-utils/e2e'

describe('我的测试', async () => {
  await setup({
    // 测试上下文选项
  })

  test('我的测试', () => {
    // ...
  })
})

在背后,setup 会在 beforeAll、beforeEach、afterEach 和 afterAll 中执行一系列任务,以正确设置 Nuxt 测试环境。

请使用下面列出的 setup 方法选项。

Nuxt 配置

  • rootDir:要进行测试的 Nuxt 应用目录路径。

    • 类型:string
    • 默认:'.'
  • configFile:配置文件的名称。

    • 类型:string
    • 默认:'nuxt.config'

时间设置

  • setupTimeout:允许运行 setupTest 完成工作的时间(毫秒),这可能包括为 Nuxt 应用构建或生成文件,具体取决于传入的选项。

    • 类型:number
    • 默认:120000(Windows 上为 240000)
  • teardownTimeout:允许拆除测试环境(如关闭浏览器)所需的时间(毫秒)。

    • 类型:number
    • 默认:30000

功能选项

  • build:是否运行单独的构建步骤。

    • 类型:boolean
    • 默认:true(当 browser 或 server 被禁用,或提供了 host 时为 false)
  • server:是否启动一个服务器以响应测试套件中的请求。

    • 类型:boolean
    • 默认:true(当提供 host 时为 false)
  • port:如果提供,将把启动的测试服务器端口设置为该值。

    • 类型:number | undefined
    • 默认:undefined
  • host:如果提供,则使用该 URL 作为测试目标,而不是构建并运行新服务器。这适用于针对已部署版本的应用,或针对已运行的本地服务器(这可能显著缩短测试执行时间)运行「真实」的端到端测试。请参阅下方的目标主机端到端示例。

    • 类型:string
    • 默认:undefined
  • browser:在底层,Nuxt 测试工具使用 playwright 进行浏览器测试。如果设置此选项,将会启动一个浏览器,并可在随后测试套件中进行控制。

    • 类型:boolean
    • 默认:false
  • browserOptions

    • 类型:包含以下属性的 object

      • type:要启动的浏览器类型 —— chromium、firefox 或 webkit
      • launch:在启动浏览器时将传递给 playwright 的选项对象。参见完整 API 参考。
  • runner:为测试套件指定运行器。目前建议使用 Vitest。

    • 类型:'vitest' | 'jest' | 'cucumber'
    • 默认:'vitest'
  • logLevel:覆盖服务器子进程的 consola 日志级别。(使用 NUXT_TEST_LOG_LEVEL 环境变量进行覆盖)

    • 类型:number
    • 默认:1
  • captureServerLogs:是否捕获服务器进程输出,而不是继承 stdio。当设置为 true(默认值)时,服务器 stdout/stderr 不会输出到控制台,而是可通过 getServerLogs() 访问。设置为 false 可恢复旧版的继承 stdio 行为(适用于在本地调试测试)。

    • 类型:boolean
    • 默认:true
目标 host 端到端示例

端到端测试的常见用例是在与你通常用于生产的环境相同的环境中对已部署的应用运行测试。

在本地开发或自动部署流水线中,对一个单独的本地服务器进行测试通常更加高效,并且通常比在测试间让测试框架重新构建要快很多。

要为端到端测试使用单独的目标主机,只需在 setup 函数中提供所需 URL 的 host 属性。

import { createPage, setup } from '@nuxt/test-utils/e2e'
import { describe, expect, it } from 'vitest'

describe('登录页面', async () => {
  await setup({
    host: 'http://localhost:8787',
  })

  it('显示电子邮件和密码字段', async () => {
    const page = await createPage('/login')
    expect(await page.getByTestId('email').isVisible()).toBe(true)
    expect(await page.getByTestId('password').isVisible()).toBe(true)
  })
})

API

$fetch(url)

获取服务端渲染页面的 HTML。

import { $fetch } from '@nuxt/test-utils/e2e'

const html = await $fetch('/')

fetch(url)

获取服务端渲染页面的响应。

import { fetch } from '@nuxt/test-utils/e2e'

const res = await fetch('/')
const { body, headers } = res

url(path)

获取给定页面的完整 URL(包括测试服务器运行的端口)。

import { url } from '@nuxt/test-utils/e2e'

const pageUrl = url('/page')
// 'http://localhost:6840/page'

getServerLogs()

返回自上次调用 startServer()(或 clearServerLogs())以来从服务器子进程 stdout/stderr 中捕获的日志行。仅当 captureServerLogs 为 true(默认值)时才会填充。

import { expect, it, vi } from 'vitest'
import { $fetch, clearServerLogs, getServerLogs } from '@nuxt/test-utils/e2e'

it('captures console.log output from a server route', async () => {
  clearServerLogs()
  await $fetch('/api/log-test')
  await vi.waitFor(() => {
    expect(getServerLogs().some(line => line.includes('[test] server-log-marker'))).toBe(true)
  })
})

clearServerLogs()

清除已捕获的服务器日志行。当你希望仅断言特定操作产生的日志时,可在请求之间使用此方法。

在浏览器中测试

我们在 @nuxt/test-utils 中为 Playwright 提供了内置支持,可以以编程方式或通过 Playwright 测试运行器使用。

createPage(url)

在 vitest、jest 或 cucumber 中,你可以使用 createPage 创建一个经过配置的 Playwright 浏览器实例,并且可以选择让它访问运行中服务器上的某个路径。你可以在 Playwright 文档 中详细了解可用的 API 方法。

传入路径时,createPage 会等待 Nuxt 应用完成水合后才返回。之后再次导航时,可以给 page.goto 传入 waitUntil: 'hydration',以再次等待水合。

import { createPage, url } from '@nuxt/test-utils/e2e'

const page = await createPage('/page')
// 你可以通过 `page` 变量访问所有 Playwright API

await page.goto(url('/other-page'), { waitUntil: 'hydration' })
在页面完成水合前就与其交互(例如在事件处理程序绑定前点击按钮)可能导致测试不稳定。

使用 Playwright 测试运行器进行测试

我们也为在 Playwright 测试运行器 中测试 Nuxt 提供了高级支持。

npm i --save-dev @playwright/test @nuxt/test-utils

你可以提供全局 Nuxt 配置,格式与前面提到的 setup() 函数相同。

playwright.config.ts
import { fileURLToPath } from 'node:url'
import { defineConfig, devices } from '@playwright/test'
import type { ConfigOptions } from '@nuxt/test-utils/playwright'

export default defineConfig<ConfigOptions>({
  use: {
    nuxt: {
      rootDir: fileURLToPath(new URL('.', import.meta.url)),
    },
  },
  // ...
})
Weitere Informationen findest du unter 查看完整示例配置.

然后你的测试文件应该直接从 @nuxt/test-utils/playwright 使用 expect 和 test:

tests/example.test.ts
import { expect, test } from '@nuxt/test-utils/playwright'

test('test', async ({ page, goto }) => {
  await goto('/', { waitUntil: 'hydration' })
  await expect(page.getByRole('heading')).toHaveText('欢迎使用 Playwright!')
})

你也可以在测试文件内直接配置 Nuxt 服务器:

tests/example.test.ts
import { expect, test } from '@nuxt/test-utils/playwright'

test.use({
  nuxt: {
    rootDir: fileURLToPath(new URL('..', import.meta.url)),
  },
})

test('test', async ({ page, goto }) => {
  await goto('/', { waitUntil: 'hydration' })
  await expect(page.getByRole('heading')).toHaveText('欢迎使用 Playwright!')
})