Тестирование
Nuxt поддерживает сквозные (E2E) и юнит-тесты через @nuxt/test-utils: это набор утилит и согласованной конфигурации, выровненный с тестами ядра Nuxt и с подходами авторов модулей в экосистеме.
Установка
@nuxt/test-utils подтягивает тестовые зависимости как опциональные peer-пакеты, чтобы вы сами выбирали версии. Например:
- вы можете выбрать между
happy-domиjsdomкак DOM-окружением для Nuxt - вы можете выбрать между
vitest,cucumber,jestиplaywrightдля сквозных тестов playwright-coreтребуется только в том случае, если вы хотите использовать встроенные в браузер утилиты тестирования (и не используете@playwright/testв качестве средства запуска тестов)
npm i --save-dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
yarn add --dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
pnpm add -D @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
bun add --dev @nuxt/test-utils vitest @vue/test-utils happy-dom playwright-core
Юнит-тестирование
Для кода, которому нужен контекст Nuxt, доступна отдельная среда выполнения в тестах. На данный момент она рассчитана на Vitest; поддержка других раннеров возможна через патчи и приветствуется.
Настройка
- По желанию добавьте
@nuxt/test-utils/moduleвnuxt.config: появится интеграция Vitest в Nuxt DevTools и запуск модульных тестов из процесса разработки.export default defineNuxtConfig({ modules: [ '@nuxt/test-utils/module', ], }) - Создайте
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, в 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'
import { fileURLToPath } from 'node:url'
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.
// @vitest-environment node
import { test } from 'vitest'
test('my test', () => {
// ... test without Nuxt environment!
})
nuxtApp. Из этого часто следуют трудно отлаживаемые ошибки.Организация тестов
При настройке с проектами Vitest тесты можно организовать так:
test/
├── e2e/
│ └── ssr.test.ts
├── nuxt/
│ ├── components.test.ts
│ └── composables.test.ts
├── unit/
│ └── utils.test.ts
Конечно, структура может быть любой, но разделять среду выполнения Nuxt и сквозные E2E-тесты 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
happy-dom или jsdom; перед прогоном поднимается глобальное приложение Nuxt (в том числе плагины и код из app.vue).Следите, чтобы не портить глобальное состояние между тестами (или явно сбрасывайте его).🎭 Встроенные моки
@nuxt/test-utils подключает встроенные заглушки (моки) для типичных API в среде DOM.
intersectionObserver
По умолчанию true — подставляется пустая заглушка класса IntersectionObserver.
indexedDB
По умолчанию false; при true подключается fake-indexeddb для рабочей имитации IndexedDB.
Их можно настроить в разделе environmentOptions файла vitest.config.ts:
import { defineVitestConfig } from '@nuxt/test-utils/config'
export default defineVitestConfig({
test: {
environmentOptions: {
nuxt: {
mock: {
intersectionObserver: true,
indexedDb: true,
},
},
},
},
})
🛠️ Вспомогательные API
@nuxt/test-utils экспортирует вспомогательные функции, которые упрощают тесты Nuxt-приложений.
mountSuspended
mountSuspended монтирует компонент Vue в контекст Nuxt: учитываются асинхронная инициализация и то, что задают плагины Nuxt (инъекции и т.п.).
mountSuspended оборачивает 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: снова учитываются асинхронная подготовка и плагины Nuxt.
Сочетайте с привычными утилитами Testing Library (screen, fireEvent и др.). Пакет @testing-library/vue нужно добавить в проект отдельно.
Между тестами Testing Library может полагаться на глобальные символы Vitest — при необходимости включите их в конфигурации 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. Например, для мока useState:
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
mockNuxtImport('useState', () => {
return () => {
return { value: 'mocked storage' }
}
})
// your tests here
Тип мока можно задать явно для типобезопасности; в фабрику передаётся оригинальная реализация — это удобно для сложных сценариев.
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
mockNuxtImport<typeof useState>('useState', (original) => {
return (...args) => {
return { ...original('some-key'), value: 'mocked state' }
}
})
// or specify the target to mock
mockNuxtImport(useState, (original) => {
return (...args) => {
return { ...original('some-key'), value: 'mocked state' }
}
})
// your tests here
mockNuxtImport для одного автоимпорта вызывается не больше одного раза на файл. На самом деле это макрос, который преобразуется в vi.mock, а vi.mock поднимается, как описано в документации Vitest.Чтобы в разных тестах подставлять разные реализации одного автоимпорта, объявите моки через vi.hoisted и передайте их в mockNuxtImport. Между тестами меняйте реализацию через API мока; не забывайте сбрасывать моки до или после теста, чтобы состояние не протекало между запусками.
import { vi } from 'vitest'
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
const { useStateMock } = vi.hoisted(() => {
return {
useStateMock: vi.fn(() => {
return { value: 'mocked storage' }
}),
}
})
mockNuxtImport('useState', () => {
return useStateMock
})
// Then, inside a test
useStateMock.mockImplementation(() => {
return { value: 'something else' }
})
Если нужно менять поведение только внутри теста, можно использовать такой приём:
import { beforeEach, vi } from 'vitest'
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
mockNuxtImport(useRoute, original => vi.fn(original))
beforeEach(() => {
vi.resetAllMocks()
})
// Then, inside a test
const useRouteOriginal = vi.mocked(useRoute).getMockImplementation()!
vi.mocked(useRoute).mockImplementation(
(...args) => ({ ...useRouteOriginal(...args), path: '/mocked' }),
)
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
Примечание. В фабрике нельзя замыкать локальные переменные извне: код поднимается (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/).
Второй — фабрика, возвращающая тело ответа.
Имитация GET /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' }),
})
У объекта допускаются поля:
handler: обработчик событияmethod: (необязательно) HTTP-метод (например'GET','POST')once: (необязательно) еслиtrue, обработчик сработает только для первого подходящего запроса и затем будет удалён
Примечание. Если запросы из компонента идут на внешний API, задайте
baseURLи обнулите его через переопределения среды в конфигурации Nuxt ($test), чтобы запросы шли на сервер Nitro.
Конфликт со сквозным (E2E) тестированием
@nuxt/test-utils/runtime и @nuxt/test-utils/e2e рассчитаны на разные среды выполнения и не смешиваются в одном файле.
Нужны и юнит-хелперы, и E2E — разнесите сценарии по файлам: для runtime либо комментарий // @vitest-environment nuxt, либо суффикс .nuxt.spec.ts в имени файла.
app.nuxt.spec.ts
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
mockNuxtImport('useState', () => {
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, автоимпорта и общего контекста), настройте Vitest так:
- Установите необходимые зависимости
npm i --save-dev vitest @vue/test-utils happy-dom @vitejs/plugin-vueyarn add --dev vitest @vue/test-utils happy-dom @vitejs/plugin-vuepnpm add -D vitest @vue/test-utils happy-dom @vitejs/plugin-vuebun add --dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue - Создайте
vitest.config.tsсо следующим содержимым:import { defineConfig } from 'vitest/config' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], test: { environment: 'happy-dom', }, }) - Добавьте в
package.jsonскрипт запуска тестов"scripts": { "build": "nuxt build", "dev": "nuxt dev", ... "test": "vitest" }, - Создайте простой компонент
<HelloWorld>components/HelloWorld.vueсо следующим содержимым:<template> <p>Hello world</p> </template> - Создайте простой модульный тест для этого компонента.
~/components/HelloWorld.spec.tsimport { 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') }) }) - Запустите Vitest
npm run testyarn testpnpm run testbun run test
После этого можно писать юнит-тесты с @vue/test-utils в проекте на Nuxt без полной среды Nuxt в Vitest.
Сквозное (E2E) тестирование
Для сквозных тестов поддерживаются раннеры Vitest, Jest, Cucumber и Playwright.
Настройка
В каждом describe, где вызываются хелперы @nuxt/test-utils/e2e, сначала нужно инициализировать контекст через setup.
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', () => {
// ...
})
})
setup регистрирует хуки beforeAll / beforeEach / afterEach / afterAll и поднимает тестовое окружение Nuxt.
Параметры setup:
Конфигурация Nuxt
rootDir: Путь к каталогу с приложением Nuxt, которое будет протестировано.- Тип:
string - По умолчанию:
'.'
- Тип:
configFile: Имя файла конфигурации.- Тип:
string - По умолчанию:
'nuxt.config'
- Тип:
Тайминги
setupTimeout: лимит времени (мс) на инициализацию (сборка/генерация артефактов Nuxt — в зависимости от опций).- Тип:
number - По умолчанию:
60000
- Тип:
Возможности
build: Следует ли запускать отдельный этап сборки.- Тип:
boolean - По умолчанию:
true(false, еслиbrowserилиserverотключены, или если указанhost)
- Тип:
server: Следует ли запускать сервер для ответа на запросы в наборе тестов.- Тип:
boolean - По умолчанию:
true(false, если указанhost)
- Тип:
port: если задан, порт поднимаемого тестового сервера будет равен этому значению.- Тип:
number | undefined - По умолчанию:
undefined
- Тип:
host: если задан, тесты идут против этого базового URL вместо подъёма отдельного сервера. Удобно для «живых» E2E против уже развёрнутого приложения или локально запущенного сервера — часто быстрее, чем каждый раз пересобирать окружение. См. пример ниже.- Тип:
string - По умолчанию:
undefined
- Тип:
browser: для тестов в браузере@nuxt/test-utilsиспользует Playwright. Приtrueбраузер запускается и доступен в рамках набора тестов.- Тип:
boolean - По умолчанию:
false
- Тип:
browserOptions- Тип: объект со следующими полями
type: тип браузера —Chromium,FirefoxилиWebKitlaunch: объект опций, передаваемых Playwright при запуске; см. справочник API
- Тип: объект со следующими полями
runner: тестовый раннер для набора. Рекомендуется Vitest.- Тип:
'vitest' | 'jest' | 'cucumber' - По умолчанию:
'vitest'
- Тип:
Пример сквозного теста целевого host
Типичный сценарий — гонять E2E против уже развёрнутого приложения (как в продакшене).
Для локальной разработки и CI часто выгоднее держать отдельный сервер и подключаться к нему: так обычно быстрее, чем каждый раз заново собирать тестовое окружение.
Передайте в setup свойство host с нужным базовым URL.
import { createPage, setup } from '@nuxt/test-utils/e2e'
import { describe, expect, it } 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'
Тестирование в браузере
Playwright в @nuxt/test-utils можно использовать из кода тестов или через штатный раннер Playwright.
createPage(url)
В Vitest, Jest или Cucumber createPage открывает страницу Playwright на уже поднятом тестовом сервере (путь — относительно его базового URL). Полный API страницы — в документации Playwright.
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.
npm i --save-dev @playwright/test @nuxt/test-utils
yarn add --dev @playwright/test @nuxt/test-utils
pnpm add -D @playwright/test @nuxt/test-utils
bun add --dev @playwright/test @nuxt/test-utils
deno add --dev npm:@playwright/test npm:@nuxt/test-utils
Глобальные параметры Nuxt в playwright.config.ts совпадают с опциями 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)),
},
},
// ...
})
В тестах импортируйте expect и test из @nuxt/test-utils/playwright:
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 можно задать и в самом файле теста через test.use:
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!')
})