Nuxtアプリケーションでは Auto Import やプラグイン、ミドルウェアといった独自のランタイム機能が多く組み込まれています。これらの機能はVitestやJest単体のテスト環境では正しく動作しないため、テストの際に「composableが見つからない」「プラグインが注入されない」といった問題に直面しがちです。@nuxt/test-utilsはこの課題を解決するNuxt公式のテストユーティリティで、Nuxtランタイム環境をテスト内で再現し、コンポーネントのマウントからAPIモックまでを一貫してサポートします。

2026年2月にはメジャーバージョンのv4.0.0がリリースされ、Vitest v4対応やモック機能の強化など大幅なアップデートが行われました。以降の内容はv4系を前提とし、v3からの移行に必要な変更点も併せて扱います。

@nuxt/test-utilsが解決する課題

Nuxtは開発の生産性を高めるために、以下のような独自機能を提供しています。

  • Auto Import: useFetchuseRouteuseStateなどのcomposableやヘルパー関数を明示的なimport文なしで使用可能にする
  • プラグイン: $fetchやカスタムプラグインがNuxtApp経由で注入される
  • ミドルウェア: ページ遷移時に認証チェックやリダイレクト処理を差し込む
  • サーバーAPI(Nitro): server/api/配下のエンドポイントがアプリと一体で動作する

通常のVitestやJestでは、これらの機能はテスト実行時に初期化されません。そのため、Auto Import対象のcomposableがundefinedになったり、useNuxtApp()がエラーを返したりします。

@nuxt/test-utilsはNuxtの起動プロセスをテスト環境内で再現し、プラグインの注入やAuto Importの解決を自動で処理します。加えて、コンポーネントの非同期セットアップへの対応、APIモック、コンポーネントスタブといったテスト専用のヘルパー関数も提供されています。

v4対応のセットアップ手順

パッケージのインストール

npm install --save-dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core

happy-domはDOMのエミュレーションに使用します。代わりにjsdomを選ぶことも可能です。Playwrightを使ったE2Eテストを実施する場合はplaywright-coreも必要です。

v4.0.0ではpeer dependencyのバージョン要件が引き上げられています。

パッケージv3系の要件v4系の要件
vitest^3.2.0^4.0.2
happy-dom*>=20.0.11
jsdom*>=27.4.0
@testing-library/vue^7.0.0 || ^8.0.1^8.0.1
@jest/globals^29.5.0 || >=30.0.0>=30.0.0

vitest.config.tsの設定

@nuxt/test-utilsが提供するdefineVitestConfigを使って設定ファイルを作成します。

// vitest.config.ts
import { defineVitestConfig } from '@nuxt/test-utils/config'

export default defineVitestConfig({
  test: {
    environment: 'nuxt',
    environmentOptions: {
      nuxt: {
        domEnvironment: 'happy-dom',
      },
    },
  },
})

package.json"type": "module"が設定されていない場合は、ファイル名をvitest.config.mtsに変更する必要があります。

マルチプロジェクト構成(推奨)

通常のユニットテストとNuxt環境テスト、E2Eテストを分離したい場合は、Vitestのprojects機能が有効です。

// vitest.config.ts
import { defineConfig } from 'vitest/config'
import { defineVitestProject } from '@nuxt/test-utils/config'

export default defineConfig({
  test: {
    projects: [
      {
        test: {
          name: 'unit',
          include: ['tests/unit/**/*.{test,spec}.ts'],
          environment: 'node',
        },
      },
      await defineVitestProject({
        test: {
          name: 'nuxt',
          include: ['tests/nuxt/**/*.{test,spec}.ts'],
          environment: 'nuxt',
        },
      }),
      {
        test: {
          name: 'e2e',
          include: ['tests/e2e/**/*.{test,spec}.ts'],
          environment: 'node',
        },
      },
    ],
  },
})

この構成により、各テスト種別が独立した環境で実行されるため、テスト間の干渉を防止できます。

Nuxt環境を有効にする3つの方法

全テストファイルに一括でNuxt環境を適用するのではなく、ファイル単位で制御する方法が3つあります。

方法1: ファイル名に.nuxt.を含める

my-component.nuxt.test.ts
my-component.nuxt.spec.ts

方法2: テストファイル内にコメントを記述

// @vitest-environment nuxt
import { describe, it, expect } from 'vitest'

方法3: vitest.config.tsでグローバルに設定

export default defineVitestConfig({
  test: {
    environment: 'nuxt',
  },
})

方法1と方法2はマルチプロジェクト構成を使わずにファイル単位で環境を切り替えたい場合に便利です。プロジェクト規模が大きくなる場合は、マルチプロジェクト構成のほうが管理しやすくなります。

テストディレクトリ構成の実例

project-root/
├── components/
│   └── UserProfile.vue
├── composables/
│   └── useUser.ts
├── server/
│   └── api/
│       └── users.ts
├── tests/
│   ├── unit/          # Nuxt環境不要のロジックテスト
│   │   └── utils.test.ts
│   ├── nuxt/          # Nuxt環境が必要なテスト
│   │   ├── UserProfile.nuxt.test.ts
│   │   └── useUser.nuxt.test.ts
│   └── e2e/           # ブラウザ・サーバー連携テスト
│       └── navigation.test.ts
├── vitest.config.ts
└── package.json

tests/nuxt/内に配置したテストファイルでは、#components#importsといったNuxtエイリアスの型サポートが自動で有効になります。

Unit Testヘルパー関数リファレンス

@nuxt/test-utilsが提供するUnit Test用のヘルパーは、すべて@nuxt/test-utils/runtimeからインポートします。

mountSuspended – 非同期コンポーネントのマウント

mountSuspendedはNuxt環境内でVueコンポーネントをマウントする関数です。Vue Test Utilsのmountとは異なり、<Suspense>でラップして処理するため、async setup()を持つコンポーネントも正しくレンダリングされます。

import { mountSuspended } from '@nuxt/test-utils/runtime'
import { UserProfile } from '#components'

describe('UserProfile', () => {
  it('ユーザー名が表示される', async () => {
    const wrapper = await mountSuspended(UserProfile, {
      props: { userId: '123' },
      route: '/users/123',
    })
    expect(wrapper.text()).toContain('テストユーザー')
  })
})

主なオプション:

オプション説明
propsコンポーネントに渡すprops
routeテスト時に想定するルートパス
slotsスロットコンテンツ
global.stubsスタブするコンポーネント

戻り値はVue Test Utilsのwrapperオブジェクトです。.text().find().trigger()などのAPIがそのまま使えます。

renderSuspended – Testing Library統合

renderSuspended@testing-library/vueのrender相当の処理をNuxt環境内で実行します。Testing Libraryのクエリ関数(getByTextgetByRoleなど)でDOMを検証したい場合に適しています。

import { renderSuspended } from '@nuxt/test-utils/runtime'
import { UserProfile } from '#components'
import { screen } from '@testing-library/vue'

describe('UserProfile', () => {
  it('アクセシブルな要素が存在する', async () => {
    await renderSuspended(UserProfile, {
      props: { userId: '123' },
    })
    expect(screen.getByRole('heading')).toBeDefined()
  })
})

renderSuspendedを使うにはVitestの設定でglobals: trueを有効にする必要があります。

mountSuspendedとの使い分け:

観点mountSuspendedrenderSuspended
ベースライブラリVue Test UtilsTesting Library
検証の軸コンポーネントの内部構造ユーザーが見るDOM
戻り値wrapperオブジェクトvoid(screenで検証)
向いているケースpropsやemitの検証、内部メソッドの呼び出しアクセシビリティ、ユーザー操作の再現

mockNuxtImport – Auto Import関数のモック

mockNuxtImportはNuxtが自動インポートする関数やcomposableをモック化します。内部的にはVitestのvi.mockに変換されてhoistingされるため、テストファイルのトップレベルで呼び出す必要があります。

import { mockNuxtImport } from '@nuxt/test-utils/runtime'

// useRouteをモック
mockNuxtImport('useRoute', () => {
  return () => ({
    params: { id: '456' },
    path: '/items/456',
  })
})

テストごとにモックの戻り値を変更する場合:

import { mockNuxtImport } from '@nuxt/test-utils/runtime'

const { useRouteMock } = vi.hoisted(() => ({
  useRouteMock: vi.fn(() => ({
    params: { id: '1' },
  })),
}))

mockNuxtImport('useRoute', () => useRouteMock)

describe('ItemPage', () => {
  it('IDが1の場合', async () => {
    useRouteMock.mockReturnValue({ params: { id: '1' } })
    // ...テストコード
  })

  it('IDが2の場合', async () => {
    useRouteMock.mockReturnValue({ params: { id: '2' } })
    // ...テストコード
  })
})

v4.0.0ではfactory関数の第一引数に元の実装が渡されるようになり、部分モックのパターンが直感的に書けるようになりました。

// v4.0.0: 元の実装を利用した部分モック
mockNuxtImport('useAuth', (original) => {
  return () => ({
    ...original(),
    isLoggedIn: true,
  })
})

mockComponent – コンポーネントのスタブ化

mockComponentは指定したコンポーネントをモックに差し替えます。コンポーネント名はPascalCase、相対パス、エイリアス(~/components/...)のいずれかで指定できます。

import { mockComponent } from '@nuxt/test-utils/runtime'

// インラインでスタブを定義
mockComponent('HeavyChart', {
  props: { data: Array },
  setup(props) {
    return () => h('div', `Chart: ${props.data.length} items`)
  },
})

// SFCファイルにリダイレクト
mockComponent('HeavyChart', () => import('./stubs/MockChart.vue'))

注意点: factory関数内ではローカル変数を参照できません。Vueのヘルパー(refhなど)が必要な場合はfactory内部でインポートしてください。

mockComponent('Counter', async () => {
  const { ref, h } = await import('vue')
  return defineComponent({
    setup() {
      const count = ref(0)
      return () => h('span', count.value)
    },
  })
})

registerEndpoint – APIレスポンスのモック

registerEndpointはNitroサーバーにモックエンドポイントを追加し、useFetch$fetchのレスポンスを制御します。

import { registerEndpoint } from '@nuxt/test-utils/runtime'

// GETリクエストのモック
registerEndpoint('/api/users/123', () => ({
  id: '123',
  name: 'テストユーザー',
  email: 'test@example.com',
}))

// POSTリクエストのモック
registerEndpoint('/api/users', {
  method: 'POST',
  handler: () => ({ id: '789', created: true }),
})

v4.0.0ではクエリパラメータ付きのURLが正しく処理されるようになり、setupファイルで登録したエンドポイントがモジュールリセット後も維持されるよう改善されています。

registerEndpointとMSW(Mock Service Worker)の違い:

観点registerEndpointMSW
仕組みNitro内部にエンドポイントを追加Service Workerでリクエストを横取り
対象useFetch/$fetch(ofetch経由)あらゆるHTTPクライアント
セットアップ1行で完結ハンドラ定義+server起動が必要
Nuxt連携ネイティブ対応ofetchとの互換性に注意が必要
推奨ケースNuxtアプリ内のAPIモック外部APIとの統合テスト

NuxtのAPIモックではregisterEndpointが最もシンプルです。ただし、外部のREST APIやGraphQLサーバーとの通信をインターセプトしたい場合はMSWとの併用も選択肢になります。その際、ofetchがNode.jsのfetchではなくネイティブHTTPリクエストを使う場合があるため、MSWのonUnhandledRequest設定でエラーにならないよう注意が必要です。

E2Eテストの実装

@nuxt/test-utilsはUnit Testだけでなく、実際にNuxtサーバーを起動してページをレンダリングするE2Eテストもサポートしています。E2E用のAPIは@nuxt/test-utils/e2eからインポートします。

E2E環境の設定

E2Eテストではテスト開始時にNuxtのビルドとサーバー起動が行われます。

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

describe('トップページ', async () => {
  await setup({
    rootDir: '.',
    server: true,
    build: true,
    setupTimeout: 120000,
  })

  it('HTMLが正しくレンダリングされる', async () => {
    const html = await $fetch('/')
    expect(html).toContain('Welcome')
  })
})

setupの主要オプションは以下のとおりです。

オプションデフォルト値説明
rootDir.Nuxtプロジェクトのルートディレクトリ
buildtrueテスト前にビルドを実行するか
servertrueテスト用サーバーを起動するか
portランダムサーバーのポート番号
browserfalsePlaywrightブラウザを起動するか
setupTimeout120000セットアップの最大待機時間(ms)
teardownTimeout30000終了処理の最大待機時間(ms)

$fetchによるサーバーレスポンス検証

$fetchを使うとテスト用サーバーに対してHTTPリクエストを送信し、レスポンスのHTML文字列やJSONを取得できます。

import { setup, $fetch } from '@nuxt/test-utils/e2e'

describe('APIエンドポイント', async () => {
  await setup({ server: true })

  it('/api/healthが正常なレスポンスを返す', async () => {
    const data = await $fetch('/api/health')
    expect(data).toEqual({ status: 'ok' })
  })

  it('トップページに見出しが含まれる', async () => {
    const html = await $fetch('/')
    expect(html).toContain('<h1>')
  })
})

Playwrightとの連携

ブラウザ上での操作やJavaScript実行を伴うテストにはPlaywrightを使います。@nuxt/test-utilsはPlaywright向けの専用ヘルパーを提供しています。

Vitest + E2Eヘルパー経由

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

describe('ナビゲーション', async () => {
  await setup({ browser: true })

  it('リンクをクリックして遷移する', async () => {
    const page = await createPage('/')
    await page.click('a[href="/about"]')
    await page.waitForURL(url('/about'))
    const heading = await page.textContent('h1')
    expect(heading).toBe('About')
  })
})

Playwright Test Runner経由

Playwrightのテストランナーを直接使う構成も可能です。

// playwright.config.ts
import { fileURLToPath } from 'node:url'
import { defineConfig } from '@playwright/test'
import type { ConfigOptions } from '@nuxt/test-utils/playwright'

export default defineConfig<ConfigOptions>({
  use: {
    nuxt: {
      rootDir: fileURLToPath(new URL('.', import.meta.url)),
    },
  },
})
// tests/e2e/home.spec.ts
import { expect, test } from '@nuxt/test-utils/playwright'

test('トップページの表示', async ({ page, goto }) => {
  await goto('/', { waitUntil: 'hydration' })
  await expect(page.getByRole('heading')).toHaveText('Welcome')
})

gotoヘルパーはテスト用サーバーのポートを自動解決するため、URLにホスト名やポート番号を含める必要はありません。waitUntil: 'hydration'を指定するとクライアントサイドのハイドレーション完了まで待機します。

Unit TestとE2Eのファイル分離ルール

@nuxt/test-utils/runtime@nuxt/test-utils/e2eは同一ファイル内で併用できません。これはそれぞれが異なるテスト環境を前提としているためです。

tests/
├── nuxt/                  # @nuxt/test-utils/runtime を使用
│   └── UserProfile.nuxt.test.ts
└── e2e/                   # @nuxt/test-utils/e2e を使用
    └── navigation.test.ts

マルチプロジェクト構成でディレクトリを分離しておけば、この制約に自然に対応できます。

v3からv4への移行ガイド

peer dependenciesの更新

v4.0.0では依存パッケージの最低バージョンが変更されています。package.jsonを以下のように更新してください。

{
  "devDependencies": {
    "@nuxt/test-utils": "^4.0.0",
    "vitest": "^4.0.2",
    "@vitest/coverage-v8": "^4.0.0",
    "happy-dom": ">=20.0.11",
    "@testing-library/vue": "^8.0.1"
  }
}

describeブロックのcomposable呼び出し制約

v4ではNuxt環境の初期化タイミングがsetupFilesからbeforeAllフックに移動しました。これにより、describeブロックのトップレベルでNuxtのcomposableを呼び出すとエラーが発生します。

// NG: v4ではエラー
describe('MyTest', () => {
  const route = useRoute() // Nuxt環境がまだ初期化されていない
})

// OK: beforeAllまたはテスト関数内で呼び出す
describe('MyTest', () => {
  let route: ReturnType<typeof useRoute>

  beforeAll(() => {
    route = useRoute()
  })

  it('ルート情報を取得できる', () => {
    expect(route.path).toBe('/')
  })
})

この変更により、vi.mockmockNuxtImportがNuxt起動前に確実に適用されるようになり、ミドルウェアやプラグイン内で使用するcomposableのモックが安定して動作します。

mockエクスポートの厳格化

Vitest v4ではvi.mockのfactory関数内で、モックしていないエクスポートにアクセスするとエラーになります(v3ではundefinedが返るだけでした)。

// NG: v4ではsomeOtherExportがモックされていないためエラー
vi.mock('~/composables/useAuth', () => ({
  useAuth: () => ({ isLoggedIn: true }),
  // someOtherExportが定義されていない
}))

// OK: importOriginalで元のエクスポートを維持
vi.mock('~/composables/useAuth', async (importOriginal) => ({
  ...(await importOriginal()),
  useAuth: () => ({ isLoggedIn: true }),
}))

テスト実装のよくある問題と対処法

Nuxt環境テストが遅い場合

Nuxt環境のテストは初回起動時にNuxtのビルドプロセスが走るため、通常のユニットテストより時間がかかります。対策として以下が有効です。

  • マルチプロジェクト構成を使い、Nuxt環境が不要なテストはenvironment: 'node'で実行する
  • テストファイルの命名規則(.nuxt.test.ts)で環境をファイル単位で制御し、必要なテストだけにNuxt環境を適用する
  • CI/CDではNuxtビルドのキャッシュ(.nuxt/ディレクトリ)を活用する

ビルトインモックの活用

@nuxt/test-utilsはブラウザAPIのモックをビルトインで提供しています。

// vitest.config.ts
export default defineVitestConfig({
  test: {
    environment: 'nuxt',
    environmentOptions: {
      nuxt: {
        mock: {
          intersectionObserver: true,  // デフォルト: 有効
          indexedDb: false,            // デフォルト: 無効
        },
      },
    },
  },
})

IntersectionObserverはデフォルトで有効です。IndexedDBを使うコンポーネントをテストする場合はindexedDb: trueに変更してください(内部ではfake-indexeddbが使用されます)。

環境変数のテスト用設定

テスト時に使用する環境変数は.env.testファイルに定義します。Nuxtは実行環境に応じて.env.testを自動で読み込みます。

# .env.test
API_BASE_URL=http://localhost:3000
AUTH_SECRET=test-secret-key

まとめ

@nuxt/test-utilsはNuxt固有のランタイム機能をテスト環境で再現するための公式パッケージです。mountSuspendedmockNuxtImportといったヘルパー関数を活用することで、Auto Importやプラグインに依存するコンポーネントやcomposableのテストが容易になります。

v4.0.0ではVitest v4への対応に加え、モックの実行タイミング改善やregisterEndpointの安定性向上が図られています。v3からの移行では、peer dependenciesの更新とdescribeブロック内のcomposable呼び出し位置の変更が主な対応事項です。

Unit TestとE2Eテストを1つのプロジェクトで管理する場合、Vitestのマルチプロジェクト構成でディレクトリを分離するのが実践的なアプローチです。@nuxt/test-utils/runtimeによるコンポーネント検証と、@nuxt/test-utils/e2eやPlaywrightによるブラウザテストを組み合わせることで、Nuxtアプリケーションの品質を多角的に担保できます。

公式ドキュメント: Nuxt Testing / GitHubリポジトリ: nuxt/test-utils