透過 100 多個技巧學習 Nuxt!

測試

如何測試您的 Nuxt 應用程式。
如果您是模組作者,您可以在模組作者指南中找到更具體的資訊。

Nuxt 透過 @nuxt/test-utils,提供對 Nuxt 應用程式端對端和單元測試的一流支援。這是一個測試工具和配置的函式庫,目前為 我們在 Nuxt 本身使用的測試以及整個模組生態系統的測試提供動力。

觀看 Alexander Lichter 關於開始使用 @nuxt/test-utils 的影片。

安裝

為了讓您管理其他測試相依性,@nuxt/test-utils 附帶各種可選的對等相依性。例如

  • 您可以選擇 happy-domjsdom 作為 Nuxt 執行環境
  • 您可以選擇 vitestcucumberjestplaywright 作為端對端測試執行器
  • 如果您希望使用內建的瀏覽器測試工具(並且未使用 @playwright/test 作為您的測試執行器),則只需要 playwright-core
npm i --save-dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core

單元測試

我們目前提供一個用於單元測試的環境,該環境需要 Nuxt 執行時環境。目前僅支援 vitest(雖然歡迎貢獻以添加其他運行時環境)。

設定

  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 執行時環境

預設情況下,@nuxt/test-utils 不會更改你的預設 Vitest 環境,因此你可以進行細粒度的選擇加入,並將 Nuxt 測試與其他單元測試一起執行。

你可以將 .nuxt. 添加到測試檔名中(例如,my-file.nuxt.test.tsmy-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! })
當你在 Nuxt 環境中執行測試時,它們將在 happy-domjsdom 環境中執行。在你的測試執行之前,將會初始化一個全域 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 文件以了解更多關於你可以傳遞的選項,以及如何使用這個工具。

例如

import { it, expect } from 'vitest'
import type { Component } from 'vue'
declare module '#components' {
  export const SomeComponent: Component
}
// ---cut---
// 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 的工具一起使用,例如 screenfireEvent。在你的專案中安裝 @testing-library/vue 以使用這些工具。

此外,Testing Library 也依賴測試全域變數進行清理。你應該在你的 Vitest 設定中開啟這些變數。

傳入的元件將會被渲染在 <div id="test-wrapper"></div> 裡面。

範例

import { it, expect } from 'vitest'
import type { Component } from 'vue'
declare module '#components' {
  export const SomeComponent: Component
}
// ---cut---
// 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
mockNuxtImport 在每個測試檔案中每個模擬匯入只能使用一次。它實際上是一個巨集,會被轉換為 vi.mock,而 vi.mock 會被提升(hoisted),如 這裡 所述。

如果你需要在測試之間模擬 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

注意:你不能在工廠函數中引用本地變數,因為它們會被提升(hoisted)。如果你需要存取 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 組合式函式、自動匯入或上下文的元件,你可以按照以下步驟進行設定。

  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 進行單元測試!祝你測試愉快!

端對端測試

對於端對端測試,我們支援 VitestJestCucumberPlaywright 作為測試執行器。

設定

在你使用 @nuxt/test-utils/e2e 輔助方法的每個 describe 區塊中,你需要先設定測試上下文才能開始。

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 測試環境。

請使用以下選項來設定 setup 方法。

Nuxt 設定

  • rootDir:要測試的 Nuxt 應用程式所在目錄的路徑。
    • 類型:string
    • 預設:'.'
  • configFile:設定檔的名稱。
    • 類型:string
    • 預設:'nuxt.config'

計時

  • setupTimeout:允許 setupTest 完成其工作(可能包括建置或產生 Nuxt 應用程式的檔案,具體取決於傳遞的選項)的時間量(以毫秒為單位)。
    • 類型:number
    • 預設:60000

功能

  • build:是否執行單獨的建置步驟。
    • 類型:boolean
    • 預設:true(如果 browserserver 被停用,或如果提供了 host,則為 false
  • server:是否啟動伺服器來響應測試套件中的請求。
    • 類型:boolean
    • 預設:true(如果提供了 host,則為 false
  • port:如果提供,將啟動的測試伺服器連接埠設定為該值。
    • 類型:number | undefined
    • 預設:undefined
  • host:如果提供,則使用一個 URL 作為測試目標,而不是建置和執行新的伺服器。適用於針對你應用程式的已部署版本,或針對已在運行的本地伺服器執行「真實」端對端測試(這可能會顯著減少測試執行時間)。請參閱下方的 目標主機端對端範例
    • 類型:string
    • 預設:undefined
  • browser:在幕後,Nuxt 測試工具使用 playwright 來執行瀏覽器測試。如果設定了此選項,將會啟動瀏覽器,並且可以在後續的測試套件中控制它。
    • 類型:boolean
    • 預設:false
  • browserOptions
    • 類型:具有以下屬性的 object
      • type:要啟動的瀏覽器類型 - 可以是 chromiumfirefoxwebkit
      • launch:將在啟動瀏覽器時傳遞給 playwright 的選項 object。請參閱完整的 API 參考資料
  • runner:指定測試套件的執行器。目前建議使用 Vitest
    • 類型:'vitest' | 'jest' | 'cucumber'
    • 預設值:'vitest'
目標 host 端對端範例

端對端測試的常見使用案例是針對通常用於生產環境的已部署應用程式執行測試。

對於本地開發或自動部署管道,針對單獨的本地伺服器進行測試可能更有效率,並且通常比允許測試框架在測試之間重建更快。

若要針對端對端測試使用單獨的目標主機,只需在 setup 函式中提供所需的 URL 作為 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)

vitestjestcucumber 中,您可以使用 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() 函式相同。

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))
    }
  },
  // ...
})
請在 參閱完整設定範例中閱讀更多資訊。

您的測試檔案應直接使用來自 @nuxt/test-utils/playwrightexpecttest

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 伺服器

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!')
})