Nuxt Nation カンファレンスが開催されます。11月12〜13日にぜひご参加ください。

テスト

Nuxtアプリケーションをテストする方法。
あなたがモジュール作成者であれば、モジュール作成者向けガイドに、より詳しい情報が記載されています。

Nuxtは、Nuxtアプリケーションのエンドツーエンドテストとユニットテストを、テストユーティリティと設定のライブラリである@nuxt/test-utilsを通じて、ファーストクラスのサポートを提供します。これは現在、Nuxt自体で使用しているテストと、モジュールエコシステム全体のテストを支えています。

@nuxt/test-utilsを使い始める方法について、Alexander Lichter氏の動画をご覧ください。

インストール

他のテスト依存関係を管理できるように、@nuxt/test-utilsは、さまざまなオプションのピア依存関係を同梱しています。たとえば、

  • Nuxtランタイム環境には、happy-domjsdomのどちらかを選択できます。
  • エンドツーエンドテストランナーには、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/modulenuxt.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-domまたはjsdom環境で実行されます。テストを実行する前に、グローバルNuxtアプリが初期化されます(たとえば、app.vueで定義したプラグインやコードを実行するなど)。つまり、テストでグローバル状態を変化させないように特に注意する必要があります(または、変化させる必要がある場合は、後でリセットする必要があります)。

🎭 組み込みモック

@nuxt/test-utilsは、DOM環境用にいくつかの組み込みモックを提供します。

intersectionObserver

デフォルトはtrue。IntersectionObserver APIの機能を持たないダミークラスを作成します。

indexedDB

デフォルトはfalsefake-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-utilsmountをラップしているため、渡すことができるオプションや、このユーティリティの使用方法については、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"' ) })
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>内にレンダリングされます。

サンプル

// 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は、テストファイルごとにモックされたインポートに対して1回しか使用できません。これは実際には、vi.mockに変換されるマクロであり、ここで説明されているように、vi.mockはホイストされます。

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のコンポーネントをモックできます。最初の引数は、パスカルケースのコンポーネント名、またはコンポーネントの相対パスを指定できます。2番目の引数は、モックされたコンポーネントを返すファクトリ関数です。

たとえば、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/)。2番目の引数は、モックされたデータを返すファクトリ関数です。

たとえば、/test/エンドポイントをモックするには、次のようにします。

import { 
registerEndpoint
} from '@nuxt/test-utils/runtime'
registerEndpoint
('/test/', () => ({
test
: 'test-field'
}))

デフォルトでは、リクエストはGETメソッドを使用して行われます。関数の代わりにオブジェクトを2番目の引数として設定することにより、別のメソッドを使用できます。

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を使用する

Nuxtのユニットテストに@vue/test-utilsを単独で使用することを好み、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を使用したユニットテストを開始する準備が整いました。楽しいテストを!

エンドツーエンドテスト

エンドツーエンドテストでは、テストランナーとしてVitestJestCucumber、およびPlaywrightをサポートしています。

セットアップ

@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は、Nuxtテスト環境を正しく設定するために、beforeAllbeforeEachafterEach、およびafterAllでいくつかのタスクを実行します。

setupメソッドには、以下のオプションを使用してください。

Nuxt設定

  • rootDir: テスト対象となるNuxtアプリが含まれるディレクトリへのパス。
    • 型: string
    • デフォルト: '.'
  • configFile: 設定ファイルの名前。
    • 型: string
    • デフォルト: 'nuxt.config'

タイミング

  • setupTimeout: setupTestがその作業を完了するまでに許可される時間(ミリ秒単位)。(これには、渡されるオプションに応じて、Nuxtアプリケーションのビルドやファイルの生成が含まれる場合があります。)
    • 型: number
    • デフォルト: 60000

機能

  • build: 別途ビルドステップを実行するかどうか。
    • 型: boolean
    • デフォルト: truebrowserまたはserverが無効の場合、またはhostが提供されている場合はfalse
  • server: テストスイート内のリクエストに応答するサーバーを起動するかどうか。
    • 型: boolean
    • デフォルト: truehostが提供されている場合はfalse
  • port: 提供されている場合、起動するテストサーバーのポートをこの値に設定します。
    • 型: number | undefined
    • デフォルト: undefined
  • host: 提供されている場合、新しいサーバーをビルドして実行する代わりに、テストターゲットとして使用するURL。デプロイ済みのアプリケーションのバージョンや、既に実行中のローカルサーバーに対して「実際の」エンドツーエンドテストを実行する場合に便利です(これにより、テスト実行時間が大幅に短縮される可能性があります)。以下のターゲットホストのエンドツーエンドの例を参照してください。
    • 型: string
    • デフォルト: undefined
  • browser: 内部的には、Nuxtテストユーティリティはplaywrightを使用してブラウザテストを実行します。このオプションが設定されている場合、ブラウザが起動され、後続のテストスイートで制御できます。
    • 型: boolean
    • デフォルト: false
  • browserOptions
    • 型: 以下のプロパティを持つobject
      • type: 起動するブラウザのタイプ - chromiumfirefox、またはwebkitのいずれか。
      • launch: ブラウザを起動する際にplaywrightに渡されるオプションのobject完全なAPIリファレンスを参照してください。
  • runner: テストスイートのランナーを指定します。現在、Vitestが推奨されています。
    • 型: 'vitest' | 'jest' | 'cucumber'
    • デフォルト: 'vitest'
ターゲットhostのエンドツーエンドの例

エンドツーエンドテストの一般的なユースケースは、通常本番環境で使用されるのと同じ環境で実行されているデプロイ済みアプリケーションに対してテストを実行することです。

ローカル開発や自動デプロイパイプラインの場合、別のローカルサーバーに対してテストする方が効率的であり、通常、テスト間でテストフレームワークがリビルドするのを許可するよりも高速です。

エンドツーエンドテストに別のターゲットホストを利用するには、setup関数のhostプロパティに目的のURLを指定するだけです。

import { 
setup
,
createPage
} from '@nuxt/test-utils/e2e'
import {
describe
,
it
,
expect
} 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)
}) })

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')
// 'http://localhost:6840/page'

ブラウザでのテスト

@nuxt/test-utils内でPlaywrightを使用した組み込みサポートを、プログラムで、またはPlaywrightテストランナー経由で提供しています。

createPage(url)

vitestjest、または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

このセクションの前述のsetup()関数と同じ設定詳細を使用して、グローバルなNuxt設定を提供できます。

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/playwrightから直接expecttestを使用する必要があります。

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