Тестирование

Как тестировать приложение Nuxt.
Если вы автор модуля, подробности — в руководстве для авторов модулей.

Nuxt предоставляет полноценную поддержку e2e и модульного тестирования через @nuxt/test-utils — набор утилит и конфигурации, который используется в тестах самого Nuxt и в экосистеме модулей.

Установка

@nuxt/test-utils поставляется с опциональными peer-зависимостями, чтобы вы могли выбирать окружение и раннеры:

  • окружение Nuxt: happy-dom или jsdom
  • e2e-раннеры: 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 (добавление других раннеров приветствуется).

Настройка

  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/unit/*.{test,spec}.ts'],
              environment: 'node',
            },
          },
          {
            test: {
              name: 'e2e',
              include: ['test/e2e/*.{test,spec}.ts'],
              environment: 'node',
            },
          },
          await defineVitestProject({
            test: {
              name: 'nuxt',
              include: ['test/nuxt/*.{test,spec}.ts'],
              environment: 'nuxt',
            },
          }),
        ],
      },
    })
    
При импорте @nuxt/test-utils в конфиг Vitest в package.json должно быть указано "type": "module", либо переименуйте конфиг, например в vitest.config.mts или vitest.config.mjs.
Переменные окружения для тестов можно задать в файле .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',
    // опционально: настройки окружения Nuxt
    // environmentOptions: {
    //   nuxt: {
    //     rootDir: fileURLToPath(new URL('./playground', import.meta.url)),
    //     domEnvironment: 'happy-dom', // 'happy-dom' (по умолчанию) или 'jsdom'
    //     overrides: {
    //       // дополнительные опции конфига Nuxt
    //     }
    //   }
    // }
  },
})

При простой настройке с environment: 'nuxt' по умолчанию можно отключить среду Nuxt для отдельного файла тестов.

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

test('my test', () => {
  // ... test without Nuxt environment!
})
Такой подход не рекомендуется: получается гибридная среда, где плагины Vite Nuxt работают, но точка входа Nuxt и nuxtApp не инициализированы. Это может приводить к сложным ошибкам.

Организация тестов

При настройке через проекты тесты можно организовать так:

Структура каталогов
test/
├── e2e/
   └── ssr.test.ts
├── nuxt/
   ├── components.test.ts
   └── composables.test.ts
├── unit/
   └── utils.test.ts

Структура может быть любой, но разделение среды выполнения Nuxt и e2e-тестов важно для стабильности.

Поддержка TypeScript в тестах

По умолчанию файлы в test/nuxt/ и tests/nuxt/ попадают в TypeScript-контекст приложения Nuxt: распознаются алиасы (~/, @/, #imports) и автоимпорты.

Рекомендуется размещать в этих каталогах только тесты, которым нужна среда Nuxt. Модульные тесты в test/unit/ и др. при необходимости добавляют в конфиг вручную.
Подключение других каталогов с тестами

Если тесты в других каталогах запускаются в среде Nuxt Vitest, добавьте их в TypeScript-контекст приложения в конфигурации:

nuxt.config.ts
export default defineNuxtConfig({
  typescript: {
    tsConfig: {
      include: [
        // путь относительно сгенерированного .nuxt/tsconfig.json
        '../test/other-nuxt-context/**/*',
      ],
    },
  },
})
Модульные тесты не должны зависеть от возможностей Nuxt (автоимпорты, композаблы). Поддержку алиасов TypeScript добавляйте только если тесты импортируют из исходников (например ~/utils/helpers), а не ради возможностей Nuxt.

Запуск тестов

При настройке через проекты можно запускать разные наборы:

# Запуск всех тестов
npx vitest

# Только модульные тесты
npx vitest --project unit

# Только тесты Nuxt
npx vitest --project nuxt

# Режим watch
npx vitest --watch
В среде Nuxt тесты выполняются в happy-dom или jsdom. Перед тестами инициализируется глобальное приложение Nuxt (в том числе плагины и код из app.vue).Не изменяйте глобальное состояние в тестах без необходимости (или сбрасывайте его после теста).

Встроенные моки

@nuxt/test-utils предоставляет встроенные моки для DOM-окружения.

intersectionObserver

По умолчанию true — создаётся заглушка без реализации для IntersectionObserver API.

indexedDB

По умолчанию false — используется fake-indexeddb для рабочего мока IndexedDB API.

Настраиваются в секции environmentOptions в vitest.config.ts:

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

export default defineVitestConfig({
  test: {
    environmentOptions: {
      nuxt: {
        mock: {
          intersectionObserver: true,
          indexedDb: true,
        },
      },
    },
  },
})

Хелперы

@nuxt/test-utils предоставляет хелперы для упрощения тестирования приложений Nuxt.

mountSuspended

mountSuspended монтирует любой Vue-компонент в среде Nuxt с поддержкой асинхронного setup и инжекций из плагинов Nuxt.

Внутри используется mount из @vue/test-utils. Подробнее об опциях и использовании — в документации Vue Test Utils.

Пример:

// @noErrors
import { expect, it } 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"',
  )
})
// @noErrors
import { expect, it } 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 рендерит Vue-компонент в среде Nuxt через @testing-library/vue, с асинхронным setup и доступом к инжекциям плагинов Nuxt.

Используйте вместе с утилитами Testing Library (screen, fireEvent). Установите @testing-library/vue в проекте.

Testing Library использует глобальные переменные для очистки — включите их в конфиге Vitest.

Компонент рендерится внутри <div id="test-wrapper"></div>.

Примеры:

// @noErrors
import { expect, it } 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()
})
// @noErrors
import { expect, it } 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' }
  }
})

// ваши тесты
mockNuxtImport можно вызвать один раз на каждый мок в файле. Это макрос, преобразуемый в vi.mock (hoisted), см. документацию Vitest.

Чтобы подменять один и тот же импорт по-разному в разных тестах, создайте моки через 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
})

// В тесте:
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) {
    // ...
  },
})

// относительный путь или алиас тоже подходят
mockComponent('~/components/my-component.vue', () => {
  // или фабрика компонента
  return defineComponent({
    setup (props) {
      // ...
    },
  })
})

// или SFC для подмены компонента
mockComponent('MyComponent', () => import('./MockComponent.vue'))

// ваши тесты

Примечание: в фабрике нельзя использовать локальные переменные (она поднимается при hoisting). Для доступа к API Vue или другим переменным импортируйте их внутри фабрики.

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 и обнулите его через переопределение конфига по окружению ($test) — тогда запросы пойдут на Nitro-сервер.

Совместимость с e2e-тестами

@nuxt/test-utils/runtime и @nuxt/test-utils/e2e работают в разных средах, в одном файле их использовать нельзя.

Чтобы совместить e2e и модульные тесты, разнесите их по разным файлам. Среду задайте комментарием // @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) настройка такая:

  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. Создайте компонент 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. Запустите тесты
    npm run test
    

После этого можно запускать модульные тесты на @vue/test-utils в Nuxt.

End-to-End тестирование

Для e2e-тестов поддерживаются раннеры Vitest, Jest, Cucumber и Playwright.

Настройка

В каждом блоке describe, где используются хелперы @nuxt/test-utils/e2e, перед тестами нужно вызвать настройку контекста.

test/my-test.spec.ts
import { describe, test } from 'vitest'
import { $fetch, setup } from '@nuxt/test-utils/e2e'

describe('Мой тест', async () => {
  await setup({
    // опции контекста теста
  })

  test('тест', () => {
    // ...
  })
})

setup внутри выполняет задачи в beforeAll, beforeEach, afterEach и afterAll для корректной подготовки среды Nuxt.

Опции метода setup:

Конфиг Nuxt

  • rootDir: путь к каталогу с приложением Nuxt для тестов.
    • Тип: string
    • По умолчанию: '.'
  • configFile: имя файла конфигурации.
    • Тип: string
    • По умолчанию: 'nuxt.config'

Таймауты

  • setupTimeout: время (мс) на завершение setupTest (включая сборку или генерацию файлов Nuxt при необходимости).
    • Тип: number
    • По умолчанию: 120000 (на Windows 240000)
  • teardownTimeout: время (мс) на завершение работы тестовой среды (например закрытие браузера).
    • Тип: number
    • По умолчанию: 30000

Опции

  • build: выполнять ли отдельный шаг сборки.
    • Тип: boolean
    • По умолчанию: true (или false, если отключены browser/server или задан host)
  • server: запускать ли сервер для запросов в тестах.
    • Тип: boolean
    • По умолчанию: true (false при заданном host)
  • port: порт тестового сервера (если задан).
    • Тип: number | undefined
    • По умолчанию: undefined
  • host: URL целевого сервера вместо сборки и запуска своего. Подходит для e2e против уже развёрнутого приложения или локального сервера (часто быстрее, чем пересборка). См. пример ниже.
    • Тип: string
    • По умолчанию: undefined
  • browser: при true запускается браузер (Playwright) для управления в тестах.
    • Тип: boolean
    • По умолчанию: false
  • browserOptions
    • Тип: object
      • type: тип браузера — chromium, firefox или webkit
      • launch: опции, передаваемые в Playwright при запуске. См. API.
  • runner: раннер тестов. Рекомендуется Vitest.
    • Тип: 'vitest' | 'jest' | 'cucumber'
    • По умолчанию: 'vitest'
Пример e2e с целевым host

Часто e2e запускают против уже развёрнутого приложения (production-подобное окружение) или отдельного локального сервера — это быстрее, чем пересборка между тестами.

Чтобы использовать внешний хост, передайте в setup опцию host с нужным URL.

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

describe('страница входа', async () => {
  await setup({
    host: 'http://localhost:8787',
  })

  it('отображает поля email и пароль', 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)

В тестах на vitest, jest или cucumber можно получить экземпляр браузера Playwright через createPage и при необходимости открыть путь на запущенном сервере. Подробнее: документация Playwright.

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

const page = await createPage('/page')
// доступны все API Playwright через переменную `page`

Тесты через раннер Playwright

Поддерживается тестирование Nuxt через раннер Playwright.

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)),
    },
  },
  // ...
})
Узнать больше Полный пример конфига.

В тестах используйте expect и test из @nuxt/test-utils/playwright:

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