測試
Nuxt 透過 @nuxt/test-utils
為您的 Nuxt 應用程式提供一流的端對端和單元測試支援,@nuxt/test-utils
是一個測試工具和設定的函式庫,目前為 Nuxt 本身使用的測試以及整個模組生態系統的測試提供支援。
安裝
為了讓您管理其他測試依賴項,@nuxt/test-utils
附帶各種可選的同級依賴項。例如
- 您可以選擇
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 外掛程式的注入。
mountSuspended
包裝了來自 @vue/test-utils
的 mount
,因此您可以查看 Vue Test Utils 文件,以了解您可以傳遞的選項以及如何使用此工具的更多資訊。例如
// 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 外掛程式的注入。
這應該與 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>"
`)
})
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 的元件。第一個引數可以是 PascalCase 中的元件名稱,或元件的相對路徑。第二個引數是回傳模擬元件的工廠函式。
例如,若要模擬 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 composable、自動匯入或內容的元件,則可以按照這些步驟進行設定。
- 安裝所需的依賴項
npm i --save-dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
- 建立一個包含以下內容的
vitest.config.ts
import {
defineConfig} from 'vitest/config' importvuefrom '@vitejs/plugin-vue' export defaultdefineConfig({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'
- 類型:
目標主機端對端範例
端對端測試的常見用例是對在與通常用於生產環境相同的環境中執行的已部署應用程式執行測試。
對於本機開發或自動化部署管道,針對單獨的本機伺服器進行測試可能更有效率,並且通常比允許測試框架在測試之間重建更快。
若要為端對端測試使用單獨的目標主機,只需提供 setup
函式的 host
屬性以及所需的 URL。
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'
在瀏覽器中測試
我們提供使用 Playwright 在 @nuxt/test-utils
內進行內建支援,無論是以程式方式還是透過 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!')
})