Тестирование
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
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 в конфиг 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!
})
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) и автоимпорты.
test/unit/ и др. при необходимости добавляют в конфиг вручную.Подключение других каталогов с тестами
Если тесты в других каталогах запускаются в среде Nuxt Vitest, добавьте их в TypeScript-контекст приложения в конфигурации:
export default defineNuxtConfig({
typescript: {
tsConfig: {
include: [
// путь относительно сгенерированного .nuxt/tsconfig.json
'../test/other-nuxt-context/**/*',
],
},
},
})
~/utils/helpers), а не ради возможностей Nuxt.Запуск тестов
При настройке через проекты можно запускать разные наборы:
# Запуск всех тестов
npx vitest
# Только модульные тесты
npx vitest --project unit
# Только тесты Nuxt
npx vitest --project nuxt
# Режим watch
npx vitest --watch
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) настройка такая:
- Установите зависимости
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" }, - Создайте компонент
app/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') }) }) - Запустите тесты
npm run testyarn testpnpm run testbun run test
После этого можно запускать модульные тесты на @vue/test-utils в Nuxt.
End-to-End тестирование
Для e2e-тестов поддерживаются раннеры Vitest, Jest, Cucumber и Playwright.
Настройка
В каждом блоке describe, где используются хелперы @nuxt/test-utils/e2e, перед тестами нужно вызвать настройку контекста.
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(на Windows240000)
- Тип:
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- Тип:
objecttype: тип браузера —chromium,firefoxилиwebkitlaunch: опции, передаваемые в 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
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 можно задать теми же опциями, что и в 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 можно задать и прямо в файле теста:
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!')
})