测试
Nuxt 通过 @nuxt/test-utils
为您的 Nuxt 应用程序提供了一流的端到端和单元测试支持,这是一个测试实用程序和配置库,目前为我们用于 Nuxt 本身和整个模块生态系统中的测试提供支持。
安装
为了让您能够管理其他测试依赖项,@nuxt/test-utils
附带各种可选的 peer 依赖项。例如
- 您可以选择
happy-dom
或jsdom
作为 Nuxt 运行时环境 - 您可以选择
vitest
、cucumber
、jest
和playwright
作为端到端测试运行器 playwright-core
仅在您希望使用内置浏览器测试实用程序(并且不使用@playwright/test
作为您的测试运行器)时才需要
npm i --save-dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
单元测试
我们目前提供了一个用于单元测试需要 Nuxt 运行时环境的代码的环境。它目前仅支持 vitest
(尽管欢迎贡献以添加其他运行时)。
设置
- 将
@nuxt/test-utils/module
添加到您的nuxt.config
文件中(可选)。它将 Vitest 集成到您的 Nuxt DevTools 中,支持在开发中运行单元测试。export default
defineNuxtConfig({modules: [ '@nuxt/test-utils/module' ] }) - 创建一个包含以下内容的
vitest.config.ts
import {
defineVitestConfig} from '@nuxt/test-utils/config' export defaultdefineVitestConfig({ // any custom Vitest config you require })
@nuxt/test-utils
时,必须在您的 package.json
中指定 "type": "module"
或相应地重命名您的 vitest 配置文件。即
vitest.config.m{ts,js}
。
.env.test
文件设置测试的环境变量。使用 Nuxt 运行时环境
默认情况下,@nuxt/test-utils
不会更改您的默认 Vitest 环境,因此您可以进行细粒度的选择并与其他单元测试一起运行 Nuxt 测试。
您可以通过将 .nuxt.
添加到测试文件的名称(例如,my-file.nuxt.test.ts
或 my-file.nuxt.spec.ts
)或在测试文件中直接添加 @vitest-environment nuxt
作为注释来选择加入 Nuxt 环境。
// @vitest-environment nuxt
import { test } from 'vitest'
test('my test', () => {
// ... test with Nuxt environment!
})
或者,您可以在 Vitest 配置中设置 environment: 'nuxt'
以为所有测试启用 Nuxt 环境。
// 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'
,则可以根据需要按测试文件选择退出默认环境。
// @vitest-environment node
import { test } from 'vitest'
test('my test', () => {
// ... test without Nuxt environment!
})
happy-dom
或 jsdom
环境中运行。在测试运行之前,将初始化一个全局 Nuxt 应用程序(包括例如运行您在 app.vue
中定义的任何插件或代码)。这意味着您应该特别注意不要在测试中修改全局状态(或者,如果您需要修改,则在之后重置它)。🎭 内置模拟
@nuxt/test-utils
为 DOM 环境提供了一些内置模拟。
intersectionObserver
默认值为 true
,为 IntersectionObserver API 创建一个没有任何功能的虚拟类
indexedDB
默认值为 false
,使用 fake-indexeddb
创建 IndexedDB API 的功能性模拟
这些可以在 vitest.config.ts
文件的 environmentOptions
部分进行配置
import { defineVitestConfig } from '@nuxt/test-utils/config'
export default defineVitestConfig({
test: {
environmentOptions: {
nuxt: {
mock: {
intersectionObserver: true,
indexedDb: true,
}
}
}
}
})
🛠️ 辅助函数
@nuxt/test-utils
提供了许多辅助函数,使测试 Nuxt 应用程序变得更容易。
mountSuspended
mountSuspended
允许您在 Nuxt 环境中挂载任何 Vue 组件,从而允许异步设置并访问 Nuxt 插件中的注入。
例如
// 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"'
)
})
import { it, expect } from 'vitest'
// ---cut---
// 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 插件中的注入。
这应该与 Testing Library 的实用程序一起使用,例如 screen
和 fireEvent
。在您的项目中安装@testing-library/vue 以使用这些。
此外,Testing Library 还依赖于测试全局变量进行清理。您应该在Vitest 配置中打开这些变量。
传入的组件将在 <div id="test-wrapper"></div>
内渲染。
示例
// 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()
})
import { it, expect } from 'vitest'
// ---cut---
// 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
,您可以这样做
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
mockNuxtImport('useStorage', () => {
return () => {
return { value: 'mocked storage' }
}
})
// your tests here
如果您需要模拟 Nuxt 导入并在测试之间提供不同的实现,您可以通过使用vi.hoisted
创建和公开您的模拟,然后在 mockNuxtImport
中使用这些模拟来实现。然后您可以访问模拟的导入,并在测试之间更改实现。请注意,在每次测试之前或之后恢复模拟以撤消运行之间的模拟状态更改。
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 的组件。第一个参数可以是大写开头的组件名称,也可以是组件的相对路径。第二个参数是一个工厂函数,返回模拟的组件。
例如,要模拟 MyComponent
,您可以
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 或其他变量,则需要在工厂函数中导入它们。
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' })
})
注意:如果组件中的请求转到外部 API,您可以使用
baseURL
,然后使用Nuxt 环境覆盖配置($test
)将其清空,以便所有请求都转到 Nitro 服务器。
与端到端测试冲突
@nuxt/test-utils/runtime
和 @nuxt/test-utils/e2e
需要在不同的测试环境中运行,因此不能在同一文件中使用。
如果您想同时使用 @nuxt/test-utils
的端到端和单元测试功能,您可以将测试拆分为单独的文件。然后,您可以使用特殊的 // @vitest-environment nuxt
注释为每个文件指定测试环境,或者使用 .nuxt.spec.ts
扩展名命名您的运行时单元测试文件。
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
如果您更喜欢单独使用 @vue/test-utils
在 Nuxt 中进行单元测试,并且您仅测试不依赖于 Nuxt 组合式函数、自动导入或上下文的组件,您可以按照以下步骤进行设置。
- 安装所需的依赖项
npm i --save-dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
- 创建一个包含以下内容的
vitest.config.ts
import { defineConfig } from 'vitest/config' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], test: { environment: 'happy-dom', }, });
- 在您的
package.json
中为测试添加一个新命令"scripts": { "build": "nuxt build", "dev": "nuxt dev", ... "test": "vitest" },
- 创建一个简单的
<HelloWorld>
组件components/HelloWorld.vue
,其内容如下<template> <p>Hello world</p> </template>
- 为此新创建的组件创建一个简单的单元测试
~/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') }) })
- 运行 vitest 命令
npm run test
恭喜,您已准备好开始使用 Nuxt 中的 @vue/test-utils
进行单元测试!祝测试愉快!
端到端测试
对于端到端测试,我们支持 Vitest、Jest、Cucumber 和 Playwright 作为测试运行器。
设置
在您利用 @nuxt/test-utils/e2e
辅助方法的每个 describe
块中,您都需要在开始之前设置测试上下文。
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
在 beforeAll
、beforeEach
、afterEach
和 afterAll
中执行了许多任务,以正确设置 Nuxt 测试环境。
请使用以下选项用于 setup
方法。
Nuxt 配置
rootDir
:要进行测试的 Nuxt 应用所在目录的路径。- 类型:
string
- 默认值:
'.'
- 类型:
configFile
:配置文件的名称。- 类型:
string
- 默认值:
'nuxt.config'
- 类型:
计时
setupTimeout
:允许setupTest
完成其工作的时间量(以毫秒为单位),这可能包括为 Nuxt 应用构建或生成文件(具体取决于传递的选项)。- 类型:
number
- 默认值:
60000
- 类型:
功能
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 的选项的object
。请参见 完整的 API 参考。
- 类型:具有以下属性的
runner
:为测试套件指定运行器。目前,建议使用 Vitest。- 类型:
'vitest' | 'jest' | 'cucumber'
- 默认值:
'vitest'
- 类型:
目标 host
端到端示例
端到端测试的一个常见用例是针对在通常用于生产环境的相同环境中运行的已部署应用程序运行测试。
对于本地开发或自动化部署管道,针对单独的本地服务器进行测试可能更有效,并且通常比允许测试框架在测试之间重建更快。
要利用单独的目标主机进行端到端测试,只需使用所需的 URL 为 setup
函数的 host
属性提供值。
import { setup, createPage } from '@nuxt/test-utils/e2e'
import { describe, it, expect } from 'vitest'
describe('login page', async () => {
await setup({
host: 'https://127.0.0.1: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)
})
})
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')
// 'https://127.0.0.1:6840/page'
在浏览器中测试
我们通过 @nuxt/test-utils
提供了内置支持,可以使用 Playwright 以编程方式或通过 Playwright 测试运行器进行测试。
createPage(url)
在 vitest
、jest
或 cucumber
中,您可以使用 createPage
创建一个配置好的 Playwright 浏览器实例,并(可选)将其指向正在运行的服务器中的路径。您可以在 Playwright 文档 中了解更多有关可用 API 方法的信息。
import { createPage } from '@nuxt/test-utils/e2e'
const page = await createPage('/page')
// you can access all the Playwright APIs from the `page` variable
使用 Playwright 测试运行器进行测试
我们还为在 Playwright 测试运行器 中测试 Nuxt 提供了首要支持。
npm i --save-dev @playwright/test @nuxt/test-utils
您可以提供全局 Nuxt 配置,其配置详细信息与本节前面提到的 setup()
函数相同。
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))
}
},
// ...
})
然后,您的测试文件应直接从 @nuxt/test-utils/playwright
中使用 expect
和 test
。
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 服务器。
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!')
})