測試

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

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

安裝

為了讓你能夠管理其他測試依賴項,@nuxt/test-utils 附帶了各種可選的對等依賴項。例如

  • 你可以選擇 happy-domjsdom 作為 Nuxt 執行時環境
  • 你可以選擇 vitestcucumberjestplaywright 作為端到端測試執行器
  • playwright-core 僅在你希望使用內建瀏覽器測試工具(並且不使用 @playwright/test 作為測試執行器)時才需要。
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 { 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',
            },
          }),
        ],
      },
    })
    
在你的 Vitest 配置中匯入 @nuxt/test-utils 時,需要在你的 package.json 中指定 "type": "module",或者相應地重新命名你的 Vitest 配置檔案。

即,vitest.config.m{ts,js}

可以透過使用 .env.test 檔案來設定測試的環境變數。

使用 Nuxt 執行時環境

使用Vitest 專案你可以對哪些測試在哪個環境中執行進行細粒度控制

  • 單元測試:將常規單元測試放在 test/unit/ 中 - 這些測試在 Node 環境中執行以提高速度
  • Nuxt 測試:將依賴 Nuxt 執行時環境的測試放在 test/nuxt/ 中 - 這些測試將在 Nuxt 執行時環境中執行

替代方案:簡單設定

如果你更喜歡簡單的設定,並希望所有測試都在 Nuxt 環境中執行,你可以使用基本配置

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' 的簡單設定,你可以根據需要選擇 退出Nuxt 環境每個測試檔案。

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

test('my test', () => {
  // ... test without Nuxt environment!
})
不建議使用此方法,因為它會建立一個混合環境,其中 Nuxt Vite 外掛執行但 Nuxt 入口和 nuxtApp 未初始化。這可能導致難以除錯的錯誤。

組織你的測試

透過基於專案的設定,你可以按以下方式組織你的測試

目錄結構
test/
├── e2e/
   └── ssr.test.ts
├── nuxt/
   ├── components.test.ts
   └── composables.test.ts
├── unit/
   └── utils.test.ts

你當然可以選擇任何測試結構,但將 Nuxt 執行時環境與 Nuxt 端到端測試分開對於測試穩定性很重要。

執行測試

透過專案設定,你可以執行不同的測試套件

# Run all tests
npx vitest

# Run only unit tests
npx vitest --project unit

# Run only Nuxt tests
npx vitest --project nuxt

# Run tests in watch mode
npx vitest --watch
當你在 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 文件瞭解更多關於你可以傳遞的選項以及如何使用此工具。

例如

// 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 的實用程式一起使用,例如 screenfireEvent。在你的專案中安裝@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
mockNuxtImport 在每個測試檔案中每個模擬匯入只能使用一次。它實際上是一個宏,會轉換為 vi.mock,而 vi.mock 會被提升,如Vitest 文件.

中所述。如果你需要模擬 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', () => {
  // 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 { $fetch, setup } 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> 元件 app/components/HelloWorld.vue,內容如下
    <template>
      <p>Hello world</p>
    </template>
    
  5. 為這個新建立的元件 ~/components/HelloWorld.spec.ts 建立一個簡單的單元測試
    import { 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')
      })
    })
    
  6. 執行 vitest 命令
    npm run test
    

恭喜,你已準備好開始使用 @vue/test-utils 在 Nuxt 中進行單元測試!祝你測試愉快!

端到端測試

對於端到端測試,我們支援Vitest, Jest, Cucumber等等Playwright作為測試執行器。

設定

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

test/my-test.spec.ts
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', () => {
    // ...
  })
})

在底層,setupbeforeAllbeforeEachafterEachafterAll 中執行多項任務,以正確設定 Nuxt 測試環境。

請使用以下選項進行 setup 方法。

Nuxt 配置

  • rootDir:包含要測試的 Nuxt 應用程式的目錄路徑。
    • 型別:string
    • 預設值:'.'
  • configFile:配置檔名稱。
    • 型別:string
    • 預設值:'nuxt.config'

時間

  • setupTimeout:允許 setupTest 完成其工作(可能包括構建或生成 Nuxt 應用程式的檔案,具體取決於傳遞的選項)的時間(以毫秒為單位)。
    • 型別:number
    • 預設值:120000 或在 Windows 上為 240000
  • teardownTimeout:允許拆除測試環境(例如關閉瀏覽器)的時間(以毫秒為單位)。
    • 型別:number
    • 預設值:30000

功能

  • 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 函式的 host 屬性提供所需的 URL。

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

describe('login page', async () => {
  await setup({
    host: 'https://: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://:6840/page'

在瀏覽器中測試

我們透過 @nuxt/test-utils 提供使用 Playwright 的內建支援,無論是透過程式設計方式還是透過 Playwright 測試執行器。

createPage(url)

vitestjestcucumber 中,你可以使用 createPage 建立一個配置好的 Playwright 瀏覽器例項,並(可選地)將其指向執行伺服器中的路徑。你可以在Playwright 文件.

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

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

中找到更多可用的 API 方法。

使用 Playwright 測試執行器進行測試我們還為在.

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

Playwright 測試執行器

中測試 Nuxt 提供一流的支援。你可以提供全域性 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)),
    },
  },
  // ...
})
playwright.config.ts

閱讀更多內容請參閱完整示例配置

你的測試檔案應該直接使用 @nuxt/test-utils/playwright 中的 expecttest
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!')
})

tests/example.test.ts

你的測試檔案應該直接使用 @nuxt/test-utils/playwright 中的 expecttest
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!')
})
這有幫助嗎?
你也可以直接在測試檔案中配置 Nuxt 伺服器