测试

如何测试你的 Nuxt 应用。
如果你是模块作者,你可以在 Module Author's guide 中找到更多具体信息。

Nuxt 通过 @nuxt/test-utils 为你的 Nuxt 应用提供一流的端到端和单元测试支持。@nuxt/test-utils 是一个测试实用程序和配置库,目前支持 我们在 Nuxt 本身上使用的测试 并在整个模块生态系统中进行测试。

¥Nuxt offers first-class support for end-to-end and unit testing of your Nuxt application via @nuxt/test-utils, a library of test utilities and configuration that currently powers the tests we use on Nuxt itself and tests throughout the module ecosystem.

安装

¥Installation

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

¥In order to allow you to manage your other testing dependencies, @nuxt/test-utils ships with various optional peer dependencies. For example:

  • 你可以选择 happy-domjsdom 作为 Nuxt 运行时环境。
  • 你可以选择 vitestcucumberjestplaywright 作为端到端测试运行器。
  • 仅当你希望使用内置浏览器测试实用程序(并且不使用 @playwright/test 作为测试运行器)时才需要 playwright-core
npm i --save-dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core

单元测试

¥Unit Testing

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

¥We currently ship an environment for unit testing code that needs a Nuxt runtime environment. It currently only has support for vitest (although contribution to add other runtimes would be welcome).

设置

¥Setup

  1. @nuxt/test-utils/module 添加到你的 nuxt.config 文件(可选)。它将 Vitest 集成添加到你的 Nuxt DevTools,以支持在开发环境中运行单元测试。
    export default defineNuxtConfig({
      modules: [
        '@nuxt/test-utils/module'
      ]
    })
    
  2. 创建一个包含以下内容的 vitest.config.ts
    import { defineVitestConfig } from '@nuxt/test-utils/config'
    
    export default defineVitestConfig({
      // any custom Vitest config you require
    })
    
在 vitest 配置中导入 @nuxt/test-utils 时,需要在 package.json 中指定 "type": "module",或者适当地重命名 vitest 配置文件。例如 vitest.config.m{ts,js}
可以使用 .env.test 文件设置用于测试的环境变量。

使用 Nuxt 运行时环境

¥Using a Nuxt Runtime Environment

默认情况下,@nuxt/test-utils 不会更改你的默认 Vitest 环境,因此你可以进行细粒度的选择加入,并将 Nuxt 测试与其他单元测试一起运行。

¥By default, @nuxt/test-utils will not change your default Vitest environment, so you can do fine-grained opt-in and run Nuxt tests together with other unit tests.

你可以通过在测试文件的名称中添加 .nuxt.(例如 my-file.nuxt.test.tsmy-file.nuxt.spec.ts)或在测试文件中直接添加 @vitest-environment nuxt 作为注释来选择加入 Nuxt 环境。

¥You can opt in to a Nuxt environment by adding .nuxt. to the test file's name (for example, my-file.nuxt.test.ts or my-file.nuxt.spec.ts) or by adding @vitest-environment nuxt as a comment directly in the test file.

// @vitest-environment nuxt
import { test } from 'vitest'

test('my test', () => {
  // ... test with Nuxt environment!
})

你也可以在 Vitest 配置中设置 environment: 'nuxt',以便为所有测试启用 Nuxt 环境。

¥You can alternatively set environment: 'nuxt' in your Vitest configuration to enable the Nuxt environment for all tests.

// vitest.config.ts
import { fileURLToPath } from 'node:url'
import { defineVitestConfig } from '@nuxt/test-utils/config'

export default defineVitestConfig({
  test: {
    environment: 'nuxt',
    // you can optionally set Nuxt-specific environment options
    // environmentOptions: {
    //   nuxt: {
    //     rootDir: fileURLToPath(new URL('./playground', import.meta.url)),
    //     domEnvironment: 'happy-dom', // 'happy-dom' (default) or 'jsdom'
    //     overrides: {
    //       // other Nuxt config you want to pass
    //     }
    //   }
    // }
  }
})

如果你已默认设置 environment: 'nuxt',则可以根据需要选择退出每个测试文件的 默认环境

¥If you have set environment: 'nuxt' by default, you can then opt out of the default environment per test file as needed.

// @vitest-environment node
import { test } from 'vitest'

test('my test', () => {
  // ... test without Nuxt environment!
})
当你在 Nuxt 环境中运行测试时,它们将在 happy-domjsdom 环境中运行。在测试运行之前,会初始化一个全局的 Nuxt 应用(例如,运行你在 app.vue 中定义的任何插件或代码)。¥When you run your tests within the Nuxt environment, they will be running in a happy-dom or jsdom environment. Before your tests run, a global Nuxt app will be initialized (including, for example, running any plugins or code you've defined in your app.vue).这意味着你应该特别注意不要在测试中改变全局状态(或者,如果需要,请在测试结束后重置它)。¥This means you should take particular care not to mutate the global state in your tests (or, if you need to, to reset it afterwards).

🎭 内置模拟

¥🎭 Built-In Mocks

@nuxt/test-utils 为 DOM 环境提供了一些内置模拟。

¥@nuxt/test-utils provides some built-in mocks for the DOM environment.

intersectionObserver

默认 true 会创建一个不包含 IntersectionObserver API 任何功能的虚拟类。

¥Default true, creates a dummy class without any functionality for the IntersectionObserver API

indexedDB

默认 false 使用 fake-indexeddb 创建 IndexedDB API 的功能模拟。

¥Default false, uses fake-indexeddb to create a functional mock of the IndexedDB API

这些可以在 vitest.config.ts 文件的 environmentOptions 部分进行配置:

¥These can be configured in the environmentOptions section of your vitest.config.ts file:

import { defineVitestConfig } from '@nuxt/test-utils/config'

export default defineVitestConfig({
  test: {
    environmentOptions: {
      nuxt: {
        mock: {
          intersectionObserver: true,
          indexedDb: true,
        }
      }
    }
  }
})

🛠️ 助手

¥🛠️ Helpers

@nuxt/test-utils 提供了许多辅助函数,使 Nuxt 应用的测试更加轻松。

¥@nuxt/test-utils provides a number of helpers to make testing Nuxt apps easier.

mountSuspended

mountSuspended 允许你在 Nuxt 环境中挂载任何 Vue 组件,从而允许异步设置并从你的 Nuxt 插件访问注入。

¥mountSuspended allows you to mount any Vue component within the Nuxt environment, allowing async setup and access to injections from your Nuxt plugins.

mountSuspended 内部封装了 @vue/test-utils 中的 mount,因此你可以查看 the Vue Test Utils documentation 来了解更多关于可以传递的选项以及如何使用此实用程序的信息。

例如:

¥For example:

// tests/components/SomeComponents.nuxt.spec.ts
import { mountSuspended } from '@nuxt/test-utils/runtime'
import { SomeComponent } from '#components'

it('can mount some component', async () => {
    const component = await mountSuspended(SomeComponent)
    expect(component.text()).toMatchInlineSnapshot(
        '"This is an auto-imported component"'
    )
})
// tests/components/SomeComponents.nuxt.spec.ts
import { mountSuspended } from '@nuxt/test-utils/runtime'
import App from '~/app.vue'

// tests/App.nuxt.spec.ts
it('can also mount an app', async () => {
    const component = await mountSuspended(App, { route: '/test' })
    expect(component.html()).toMatchInlineSnapshot(`
      "<div>This is an auto-imported component</div>
      <div> I am a global component </div>
      <div>/</div>
      <a href="/test"> Test link </a>"
    `)
})

renderSuspended

renderSuspended 允许你使用 @testing-library/vue 在 Nuxt 环境中渲染任何 Vue 组件,从而允许异步设置和访问 Nuxt 插件的注入。

¥renderSuspended allows you to render any Vue component within the Nuxt environment using @testing-library/vue, allowing async setup and access to injections from your Nuxt plugins.

此设置应与测试库中的实用程序(例如 screenfireEvent)一起使用。在你的项目中安装 @testing-library/vue 即可使用这些资源。

¥This should be used together with utilities from Testing Library, e.g. screen and fireEvent. Install @testing-library/vue in your project to use these.

此外,测试库还依赖于测试全局变量进行清理。你应该在 Vitest 配置 中启用这些功能。

¥Additionally, Testing Library also relies on testing globals for cleanup. You should turn these on in your Vitest config.

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

¥The passed in component will be rendered inside a <div id="test-wrapper"></div>.

示例:

¥Examples:

// tests/components/SomeComponents.nuxt.spec.ts
import { renderSuspended } from '@nuxt/test-utils/runtime'
import { SomeComponent } from '#components'
import { screen } from '@testing-library/vue'

it('can render some component', async () => {
  await renderSuspended(SomeComponent)
  expect(screen.getByText('This is an auto-imported component')).toBeDefined()
})
// tests/App.nuxt.spec.ts
import { renderSuspended } from '@nuxt/test-utils/runtime'
import App from '~/app.vue'

it('can also render an app', async () => {
  const html = await renderSuspended(App, { route: '/test' })
  expect(html).toMatchInlineSnapshot(`
    "<div id="test-wrapper">
      <div>This is an auto-imported component</div>
      <div> I am a global component </div>
      <div>Index page</div><a href="/test"> Test link </a>
    </div>"
  `)
})

mockNuxtImport

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

¥mockNuxtImport allows you to mock Nuxt's auto import functionality. For example, to mock useStorage, you can do so like this:

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

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

// your tests here
每个测试文件的每个模拟导入只能使用一次 mockNuxtImport。它实际上是一个宏,会转换为 vi.mock,而 vi.mock 会被提升,正如 here 中所述。

如果你需要模拟 Nuxt 导入并在测试之间提供不同的实现,你可以使用 vi.hoisted 创建并公开模拟,然后在 mockNuxtImport 中使用这些模拟。然后,你就可以访问模拟导入,并可以在测试之间更改实现。在每次测试之前或之后,请务必使用 恢复模拟 来撤消运行期间模拟状态的更改。

¥If you need to mock a Nuxt import and provide different implementations between tests, you can do it by creating and exposing your mocks using vi.hoisted, and then use those mocks in mockNuxtImport. You then have access to the mocked imports, and can change the implementation between tests. Be careful to restore mocks before or after each test to undo mock state changes between runs.

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

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

mockNuxtImport('useStorage', () => {
  return useStorageMock
})

// Then, inside a test
useStorageMock.mockImplementation(() => {
  return { value: 'something else' }
})

mockComponent

mockComponent 允许你模拟 Nuxt 的组件。第一个参数可以是采用帕斯卡命名法 (PascalCase) 的组件名称,也可以是组件的相对路径。第二个参数是一个工厂函数,它返回模拟组件。

¥mockComponent allows you to mock Nuxt's component. The first argument can be the component name in PascalCase, or the relative path of the component. The second argument is a factory function that returns the mocked component.

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

¥For example, to mock MyComponent, you can:

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

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

// relative path or alias also works
mockComponent('~/components/my-component.vue', async () => {
  // or a factory function
  return defineComponent({
    setup(props) {
      // ...
    }
  })
})

// or you can use SFC for redirecting to a mock component
mockComponent('MyComponent', () => import('./MockComponent.vue'))

// your tests here

注意:你无法在工厂函数中引用局部变量,因为它们会被提升。如果你需要访问 Vue API 或其他变量,则需要在工厂函数中导入它们。

¥Note: You can't reference local variables in the factory function since they are hoisted. If you need to access Vue APIs or other variables, you need to import them in your factory function.

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 发出请求以显示某些数据的组件,它会非常方便。

¥registerEndpoint allows you create Nitro endpoint that returns mocked data. It can come in handy if you want to test a component that makes requests to API to display some data.

第一个参数是端点名称(例如 /test/)。第二个参数是一个工厂函数,它返回模拟数据。

¥The first argument is the endpoint name (e.g. /test/). The second argument is a factory function that returns the mocked data.

例如,要模拟 /test/ 端点,你可以执行以下操作:

¥For example, to mock /test/ endpoint, you can do:

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

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

默认情况下,你的请求将使用 GET 方法发出。你可以使用另一种方法,将对象而不是函数设置为第二个参数。

¥By default, your request will be made using the GET method. You may use another method by setting an object as the second argument instead of a function.

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

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

注意:如果你在组件中的请求发送到外部 API,你可以使用 baseURL,然后使用 Nuxt 环境覆盖配置$test)将其清空,这样你的所有请求都将发送到 Nitro 服务器。

¥Note: If your requests in a component go to an external API, you can use baseURL and then make it empty using Nuxt Environment Override Config ($test) so all your requests will go to Nitro server.

与端到端冲突测试

¥Conflict with End-To-End Testing

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

¥@nuxt/test-utils/runtime and @nuxt/test-utils/e2e need to run in different testing environments and so can't be used in the same file.

如果你希望同时使用 @nuxt/test-utils 的端到端和单元测试功能,你可以将测试拆分成单独的文件。然后,你可以使用特殊的 // @vitest-environment nuxt 注释为每个文件指定一个测试环境,或者使用 .nuxt.spec.ts 扩展名命名你的运行时单元测试文件。

¥If you would like to use both the end-to-end and unit testing functionality of @nuxt/test-utils, you can split your tests into separate files. You then either specify a test environment per-file with the special // @vitest-environment nuxt comment, or name your runtime unit test files with the .nuxt.spec.ts extension.

app.nuxt.spec.ts

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

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

app.e2e.spec.ts

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

await setup({
  setupTimeout: 10000,
})

// ...

使用 @vue/test-utils

¥Using @vue/test-utils

如果你更喜欢在 Nuxt 中单独使用 @vue/test-utils 进行单元测试,并且你只测试不依赖于 Nuxt 可组合项、自动导入或上下文的组件,则可以按照以下步骤进行设置。

¥If you prefer to use @vue/test-utils on its own for unit testing in Nuxt, and you are only testing components which do not rely on Nuxt composables, auto-imports or context, you can follow these steps to set it up.

  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> 组件 components/HelloWorld.vue,其内容如下:
    <template>
      <p>Hello world</p>
    </template>
    
  5. 为这个新创建的组件 ~/components/HelloWorld.spec.ts 创建一个简单的单元测试
    import { describe, it, expect } from 'vitest'
    import { mount } from '@vue/test-utils'
    
    import HelloWorld from './HelloWorld.vue'
    
    describe('HelloWorld', () => {
      it('component renders Hello world properly', () => {
        const wrapper = mount(HelloWorld)
        expect(wrapper.text()).toContain('Hello world')
      })
    })
    
  6. 运行 vitest 命令
    npm run test
    

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

¥Congratulations, you're all set to start unit testing with @vue/test-utils in Nuxt! Happy testing!

端到端测试

¥End-To-End Testing

对于端到端测试,我们支持 VitestJestCucumberPlaywright 作为测试运行器。

¥For end-to-end testing, we support Vitest, Jest, Cucumber and Playwright as test runners.

设置

¥Setup

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

¥In each describe block where you are taking advantage of the @nuxt/test-utils/e2e helper methods, you will need to set up the test context before beginning.

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

describe('My test', async () => {
  await setup({
    // test context options
  })

  test('my test', () => {
    // ...
  })
})

在后台,setup 会在 beforeAllbeforeEachafterEachafterAll 中执行一系列任务,以正确设置 Nuxt 测试环境。

¥Behind the scenes, setup performs a number of tasks in beforeAll, beforeEach, afterEach and afterAll to set up the Nuxt test environment correctly.

请使用以下选项作为 setup 方法。

¥Please use the options below for the setup method.

Nuxt 配置

¥Nuxt Config

  • rootDir:要测试的 Nuxt 应用所在目录的路径。
    • 类型:string
    • 默认:'.'
  • configFile:配置文件的名称。
    • 类型:string
    • 默认:'nuxt.config'

时间

¥Timings

  • setupTimeoutsetupTest 完成其工作所需的时间(以毫秒为单位)(这可能包括为 Nuxt 应用构建或生成文件,具体取决于传递的选项)。
    • 类型:number
    • 默认:60000

功能

¥Features

  • build:是否运行单独的构建步骤。
    • 类型:boolean
    • 默认:true(如果 browserserver 被禁用,或者提供了 host,则为 false
  • server:是否启动服务器以响应测试套件中的请求。
    • 类型:boolean
    • 默认:true(如果提供了 host,则为 false
  • port:如果提供,则将启动的测试服务器端口设置为该值。
    • 类型:number | undefined
    • 默认:undefined
  • host:如果提供,则提供一个用作测试目标的 URL,而不是构建和运行新的服务器。用于针对已部署的应用版本或已运行的本地服务器(这可以显著缩短测试执行时间)运行 "real" 端到端测试。参见 目标主机端到端示例如下
    • 类型:string
    • 默认:undefined
  • browser:Nuxt 测试工具内部使用 playwright 进行浏览器测试。如果设置了此选项,将启动浏览器,并可在后续测试套件中进行控制。
    • 类型:boolean
    • 默认:false
  • browserOptions
    • 类型:object 具有以下属性
      • type:要启动的浏览器类型 - chromiumfirefoxwebkit
      • launch:启动浏览器时将传递给 playwright 的选项 object 模块。请参阅 完整的 API 参考
  • runner:指定测试套件的运行器。目前推荐使用 Vitest
    • 类型:'vitest' | 'jest' | 'cucumber'
    • 默认:'vitest'

Target host 端到端示例

¥Target host end-to-end example

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

¥A common use-case for end-to-end testing is running the tests against a deployed application running in the same environment typically used for Production.

对于本地开发或自动部署管道,使用单独的本地服务器进行测试可能更高效,并且通常比允许测试框架在测试之间重建更快。

¥For local development or automated deploy pipelines, testing against a separate local server can be more efficient and is typically faster than allowing the test framework to rebuild between tests.

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

¥To utilize a separate target host for end-to-end tests, simply provide the host property of the setup function with the desired URL.

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

describe('login page', async () => {
  await setup({
    host: 'http://localhost:8787',
  })

  it('displays the email and password fields', async () => {
    const page = await createPage('/login')
    expect(await page.getByTestId('email').isVisible()).toBe(true)
    expect(await page.getByTestId('password').isVisible()).toBe(true)
  })
})

APIs

$fetch(url)

从上下文中获取 Nuxt 实例。

¥Get the HTML of a server-rendered page.

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

const html = await $fetch('/')

fetch(url)

根据上述示例,使用以下命令获取 :

¥Get the response of a server-rendered page.

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

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

url(path)

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

¥Get the full URL for a given page (including the port the test server is running on.)

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

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

在浏览器中测试

¥Testing in a Browser

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

¥We provide built-in support using Playwright within @nuxt/test-utils, either programmatically or via the Playwright test runner.

createPage(url)

vitestjestcucumber 中,你可以使用 createPage 创建已配置的 Playwright 浏览器实例,并(可选)将其指向正在运行的服务器的路径。你可以了解有关 在 Playwright 中文档 中可用的 API 方法的更多信息。

¥Within vitest, jest or cucumber, you can create a configured Playwright browser instance with createPage, and (optionally) point it at a path from the running server. You can find out more about the API methods available from in the Playwright documentation.

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

const page = await createPage('/page')
// you can access all the Playwright APIs from the `page` variable

使用 Playwright Test Runner 进行测试

¥Testing with Playwright Test Runner

我们还提供在 Playwright 测试运行器 中测试 Nuxt 的优质支持。

¥We also provide first-class support for testing Nuxt within the Playwright test runner.

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

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

¥You can provide global Nuxt configuration, with the same configuration details as the setup() function mentioned earlier in this section.

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))
    }
  },
  // ...
})

:

Read more in See full example config.

::

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

¥Your test file should then use expect and test directly from @nuxt/test-utils/playwright:

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('Welcome to Playwright!')
})

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

¥You can alternatively configure your Nuxt server directly within your test file:

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('Welcome to Playwright!')
})