为了让你管理其他测试依赖,@nuxt/test-utils 附带了各种可选的 peer 依赖。例如:
happy-dom 或 jsdomvitest、cucumber、jest 或 playwright@playwright/test 作为测试运行器)时,才需要 playwright-corenpm i --save-dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
yarn add --dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
pnpm add -D @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
bun add --dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
我们目前为需要 Nuxt 运行时环境的代码提供了一个单元测试环境。当前「仅支持 vitest」(欢迎贡献以添加其他运行时)。
nuxt.config 文件中添加 @nuxt/test-utils/module。它会向 Nuxt DevTools 添加一个 Vitest 集成,支持在开发时运行你的单元测试。export default defineNuxtConfig({
modules: [
'@nuxt/test-utils/module',
],
})
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/{e2e,unit}/*.{test,spec}.ts'],
environment: 'node',
},
},
await defineVitestProject({
test: {
name: 'nuxt',
include: ['test/nuxt/*.{test,spec}.ts'],
environment: 'nuxt',
},
}),
],
},
})
@nuxt/test-utils 时,必须在 package.json 中指定 "type": "module" 或者适当重命名你的 vitest 配置文件。例如:
vitest.config.m{ts,js}。
.env.test 文件为测试设置环境变量。使用 Vitest 项目,你可以精确控制哪些测试在何种环境中运行:
test/unit/ —— 这些在 Node 环境中运行以提高速度test/nuxt/ —— 这些将在 Nuxt 运行时环境中运行如果你更喜欢更简单的设置并希望所有测试都在 Nuxt 环境中运行,可以使用基础配置:
import { defineVitestConfig } from '@nuxt/test-utils/config'
export default defineVitestConfig({
test: {
environment: 'nuxt',
// 你可以可选地设置 Nuxt 特定的环境选项
// environmentOptions: {
// nuxt: {
// rootDir: fileURLToPath(new URL('./playground', import.meta.url)),
// domEnvironment: 'happy-dom', // 'happy-dom'(默认)或 'jsdom'
// overrides: {
// // 你想传入的其他 Nuxt 配置
// }
// }
// }
},
})
如果你使用默认的 environment: 'nuxt' 的简单设置,你可以根据需要在每个测试文件中通过特殊注释选择退出 Nuxt 环境。
// @vitest-environment node
import { test } from 'vitest'
test('my test', () => {
// ... 在没有 Nuxt 环境的情况下测试!
})
nuxtApp 可能没有被初始化。这可能导致难以调试的错误。使用基于项目的设置,你可能会如下组织你的测试:
test/
├── e2e/
│ └── ssr.test.ts
├── nuxt/
│ ├── components.test.ts
│ └── composables.test.ts
├── unit/
│ └── utils.test.ts
当然你可以选择任意测试结构,但将 Nuxt 运行时环境与 Nuxt 端到端测试分开对测试稳定性很重要。
默认情况下,test/nuxt/ 和 tests/nuxt/ 目录下的测试文件会被包含在 Nuxt 应用的 TypeScript 上下文 中。这意味着它们能够识别 Nuxt 的别名(如 ~/、@/、#imports),并且 TypeScript 也会识别在你的 Nuxt 应用中可用的自动导入。
test/unit/)中的单元测试可以根据需要手动添加。如果您在Nuxt Vitest环境中运行的其他目录中有测试,您可以通过将它们添加到配置中,将它们包含在Nuxt应用程序TypeScript上下文中:
export default defineNuxtConfig({
typescript: {
tsConfig: {
include: [
// this path is relative to the generated .nuxt/tsconfig.json
'../test/other-nuxt-context/**/*',
],
},
},
})
~/utils/helpers)导入时,才添加 TypeScript 路径别名支持,而不是针对 Nuxt 特定功能。使用项目设置,你可以运行不同的测试套件:
# 运行所有测试
npx vitest
# 仅运行单元测试
npx vitest --project unit
# 仅运行 Nuxt 测试
npx vitest --project nuxt
# 以监听模式运行测试
npx vitest --watch
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 应用。
mountSuspendedmountSuspended 允许你在 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"',
)
})
// 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>"
`)
})
renderSuspendedrenderSuspended 允许你在 Nuxt 环境中使用 @testing-library/vue 来渲染任意 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()
})
// 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>"
`)
})
mockNuxtImportmockNuxtImport 允许你模拟 Nuxt 的自动导入功能。例如,要模拟 useStorage,可以这样做:
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
mockNuxtImport('useStorage', () => {
return () => {
return { value: 'mocked storage' }
}
})
// your tests here
如果你需要在不同测试之间为 Nuxt 导入提供不同的实现,可以通过创建并暴露你的模拟(使用 vi.hoisted)来实现,然后在 mockNuxtImport 中使用这些模拟。这样你可以访问被模拟的导入,并在测试之间更改实现。注意在每个测试前后恢复模拟状态以撤销模拟的状态更改(参见 restore mocks)。
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' }
})
mockComponentmockComponent 允许你模拟 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'))
// 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)
},
})
})
registerEndpointregisterEndpoint 允许你创建返回模拟数据的 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 { $fetch, setup } from '@nuxt/test-utils/e2e'
await setup({
setupTimeout: 10000,
})
// ...
@vue/test-utils如果你更愿意单独使用 @vue/test-utils 在 Nuxt 中进行单元测试,且你仅测试不依赖 Nuxt composables、自动导入或上下文的组件,可以按以下步骤设置。
npm i --save-dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
yarn add --dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
pnpm add -D vitest @vue/test-utils happy-dom @vitejs/plugin-vue
bun add --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> 组件 app/components/HelloWorld.vue,内容如下:<template>
<p>Hello world</p>
</template>
~/components/HelloWorld.spec.tsimport { describe, expect, it } 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')
})
})
npm run test
yarn test
pnpm run test
bun run test
恭喜,你现在已准备好在 Nuxt 中使用 @vue/test-utils 进行单元测试!祝测试愉快!
对于端到端测试,我们支持 Vitest、Jest、Cucumber 和 Playwright 作为测试运行器。
在每个使用 @nuxt/test-utils/e2e 辅助方法的 describe 块中,你需要在开始之前设置测试上下文。
import { describe, test } from 'vitest'
import { $fetch, setup } from '@nuxt/test-utils/e2e'
describe('My test', async () => {
await setup({
// test context options
})
test('my test', () => {
// ...
})
})
在背后,setup 会在 beforeAll、beforeEach、afterEach 和 afterAll 中执行一系列任务,以正确设置 Nuxt 测试环境。
请使用下面列出的 setup 方法选项。
rootDir:要进行测试的 Nuxt 应用目录路径。
string'.'configFile:配置文件的名称。
string'nuxt.config'setupTimeout:允许运行 setupTest 完成工作的时间(毫秒),这可能包括为 Nuxt 应用构建或生成文件,具体取决于传入的选项。number120000(Windows 上为 240000)teardownTimeout:允许拆除测试环境(如关闭浏览器)所需的时间(毫秒)。number30000build:是否运行单独的构建步骤。booleantrue(当 browser 或 server 被禁用,或提供了 host 时为 false)server:是否启动一个服务器以响应测试套件中的请求。booleantrue(当提供 host 时为 false)port:如果提供,将把启动的测试服务器端口设置为该值。number | undefinedundefinedhost:如果提供,则使用该 URL 作为测试目标,而不是构建并运行新的服务器。适用于对已部署的应用或已在本地运行的服务器进行“真实”的端到端测试(通常能显著减少测试执行时间)。参见下面的 目标主机端到端示例。stringundefinedbrowser:在底层,Nuxt 测试工具使用 playwright 进行浏览器测试。如果设置此选项,将会启动一个浏览器,并可在随后测试套件中进行控制。booleanfalsebrowserOptionsobjecttype:要启动的浏览器类型 —— chromium、firefox 或 webkitlaunch:在启动浏览器时将传递给 playwright 的选项对象。参见 完整 API 参考。runner:为测试套件指定运行器。目前建议使用 Vitest。'vitest' | 'jest' | 'cucumber''vitest'host 端到端示例端到端测试的常见用例是在与你通常用于生产的环境相同的环境中对已部署的应用运行测试。
在本地开发或自动部署流水线中,对一个单独的本地服务器进行测试通常更加高效,并且通常比在测试间让测试框架重新构建要快很多。
要为端到端测试使用单独的目标主机,只需在 setup 函数中提供所需 URL 的 host 属性。
import { createPage, setup } from '@nuxt/test-utils/e2e'
import { describe, expect, it } 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)
})
})
$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'
我们在 @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')
// 你可以通过 `page` 变量访问所有 Playwright API
我们也为在 Playwright 测试运行器 中测试 Nuxt 提供了高级支持。
npm i --save-dev @playwright/test @nuxt/test-utils
yarn add --dev @playwright/test @nuxt/test-utils
pnpm add -D @playwright/test @nuxt/test-utils
bun add --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!')
})