Тестирование
Nuxt предлагает первоклассную поддержку для сквозного (end-to-end) и юнит тестирования приложения Nuxt с помощью @nuxt/test-utils
, библиотеки тестовых утилит и конфигурации, которая в настоящее время поддерживает тесты, которые мы используем в самом Nuxt и тесты по всей экосистеме модулей.
Установка
Чтобы вы могли управлять другими зависимостями тестирования, @nuxt/test-utils
поставляется с различными необязательными равнозначными зависимостями. Например:
- вы можете выбрать между
happy-dom
иjsdom
для runtime Nuxt - вы можете выбрать между
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
(хотя вклад в добавление других сред выполнения будет приветствоваться).
Настройка
- Добавьте
@nuxt/test-utils/module
в файлnuxt.config
(опционально). Это добавляет интеграцию Vitest в Nuxt DevTools, которая поддерживает запуск ваших модульных тестов в процессе разработки.export default
defineNuxtConfig({modules: [ '@nuxt/test-utils/module' ] }) - Создайте
vitest.config.ts
со следующим содержимым:import {
defineVitestConfig} from '@nuxt/test-utils/config' export defaultdefineVitestConfig({ // любая необходимая пользовательская конфигурация Vitest })
@nuxt/test-utils
в конфигурацию vitest, необходимо указать "type": "module"
в package.json
или переименовать файл конфигурации vitest соответствующим образом.Т.е.
vitest.config.m{ts,js}
.
Использование среды выполнения Nuxt
По умолчанию, @nuxt/test-utils
не изменяет окружение Vitest, поэтому вы можете выполнить детальную настройку и запустить тесты Nuxt вместе с другими юнит-тестами.
Вы можете включить окружение Nuxt, добавив .nuxt.
к имени тестового файла (например, my-file.nuxt.test.ts
или my-file.nuxt.spec.ts
) или добавив @vitest-environment nuxt
в качестве комментария непосредственно в тестовом файле.
// @vitest-environment nuxt
import { test } from 'vitest'
test('my test', () => {
// ... тест в среде выполнения Nuxt!
})
В качестве альтернативы вы можете установить environment: 'nuxt'
в конфигурации Vitest, чтобы включить окружение Nuxt для всех тестов.
// vitest.config.ts
import { fileURLToPath } from 'node:url'
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'
по умолчанию, вы можете отказаться от окружения по умолчанию для каждого тестового файла по мере необходимости.
// @vitest-environment node
import { test } from 'vitest'
test('my test', () => {
// ... тест без окружения Nuxt!
})
happy-dom
или jsdom
. Перед запуском ваших тестов будет инициализировано глобальное приложение Nuxt (включая, например, запуск любых плагинов или кода, которые вы определили в своем app.vue
).Это означает, что вам следует проявлять особую осторожность, чтобы не изменять глобальное состояние в своих тестах (или, если это необходимо, сбрасывать его впоследствии).🎭 Встроенные моки
@nuxt/test-utils
предоставляет несколько встроенных моков для среды DOM.
intersectionObserver
Значение по умолчанию - true
, создает фиктивный класс без какой-либо функциональности для IntersectionObserver API
indexedDB
Значение по умолчанию - false
, используется fake-indexeddb
для создания функциональной имитации API IndexedDB.
Их можно настроить в разделе 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, позволяя асинхронную настройку и доступ к инъекциям из плагинов Nuxt.
mountSuspended
wraps mount
from @vue/test-utils
, so you can check out the Vue Test Utils documentation for more on the options you can pass, and how to use this utility.Например:
// 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(
'"Это автоматически импортируемый компонент."'
)
})
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>Это автоматически импортируемый компонент</div>
<div> Я глобальный компонент </div>
<div>/</div>
<a href="/test"> Тестовая ссылка </a>"
`)
})
renderSuspended
renderSuspended
позволяет отрисовать любой компонент Vue в окружении Nuxt с помощью @testing-library/vue
, обеспечивая асинхронную настройку и доступ к инъекциям из плагинов Nuxt.
renderSuspended
следует использовать вместе с утилитами из Testing Library, например screen
и fireEvent
. Установите @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('Это автоматически импортируемый компонент')).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>Это автоматически импортируемый компонент</div>
<div> Я глобальный компонент </div>
<div>Главная страница</div><a href="/test"> Тестовая ссылка </a>
</div>"
`)
})
mockNuxtImport
mockNuxtImport
позволяет вам имитировать функцию автоматического импорта Nuxt. Например, чтобы имитировать useStorage
, вы можете сделать это следующее:
import { mockNuxtImport } from '@nuxt/test-utils/runtime'
mockNuxtImport('useStorage', () => {
return () => {
return { value: 'mocked storage' }
}
})
// здесь ваши тесты
mockNuxtImport
можно использовать для мока импорта только один раз на тестовый файл. На самом деле это макрос, который преобразуется в 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().mockImplementation(() => {
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', async () => {
// или фабричная функция
return defineComponent({
setup(props) {
// ...
}
})
})
// или вы можете использовать SFC для перенаправления на мок компонента
mockComponent('MyComponent', () => import('./MockComponent.vue'))
// здесь ваши тесты
Примечание: Вы не можете ссылаться на локальные переменные в фабричной функции, потому что они поднимаются (hoisted). Если вам нужно получить доступ к 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
, а затем сделать его пустым с помощью Nuxt Environment Override Config ($test
), чтобы все ваши запросы направлялись на сервер Nitro.
Конфликт со сквозным тестированием
@nuxt/test-utils/runtime
и @nuxt/test-utils/e2e
должны запускаться в разных тестовых средах, поэтому их нельзя использовать в одном файле.
Если вы хотите использовать как сквозную, так и модульную функциональность тестирования @nuxt/test-utils
, вы можете разделить свои тесты на отдельные файлы. Затем вы либо указываете тестовую среду для каждого файла с помощью специального комментария // @vitest-environment nuxt
, либо называете файлы модульных runtime тестов с расширением .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
Если вы предпочитаете использовать @vue/test-utils
отдельно для юнит-тестирования в Nuxt и тестируете только компоненты, которые не полагаются на композаблы Nuxt, автоматический импорт или контекст, вы можете выполнить следующие шаги для его настройки.
- Установите необходимые зависимости
npm i --save-dev vitest @vue/test-utils happy-dom @vitejs/plugin-vue
- Создайте
vitest.config.ts
со следующим содержимым:import {
defineConfig} from 'vitest/config' importvuefrom '@vitejs/plugin-vue' export defaultdefineConfig({plugins: [vue()],test: {environment: 'happy-dom', }, }); - Добавьте новую команду для теста в ваш
package.json
"scripts": { "build": "nuxt build", "dev": "nuxt dev", ... "test": "vitest" },
- Создайте простой компонент
<HelloWorld>
components/HelloWorld.vue
со следующим содержимым:<template> <p>Привет, мир!</p> </template>
- Создайте простой модульный тест для этого компонента.
~/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('Привет, мир!') }) })
- Запустить команду vitest
npm run test
Поздравляем, вы готовы начать модульное тестирование с @vue/test-utils
в Nuxt! Удачного тестирования!
Сквозное (End-To-End) Тестирование
Для сквозного тестирования мы поддерживаем в качестве тест-раннеров: Vitest, Jest, Cucumber и Playwright.
Настройка
В каждом блоке describe
, где вы используете вспомогательные методы @nuxt/test-utils/e2e
, вам необходимо настроить тестовый контекст перед началом.
import { describe, test } from 'vitest'
import { setup, $fetch } from '@nuxt/test-utils/e2e'
describe('My test', async () => {
await setup({
// параметры тестового контекста
})
test('my test', () => {
// ...
})
})
"За кулисами" setup
выполняет ряд задач в beforeAll
, beforeEach
, afterEach
и afterAll
для правильной настройки тестовой среды Nuxt.
Пожалуйста, используйте приведенные ниже параметры для метода setup
.
Конфигурация Nuxt
rootDir
: Путь к каталогу с приложением Nuxt, которое будет протестировано.- Тип:
string
- По умолчанию:
'.'
- Тип:
configFile
: Имя файла конфигурации.- Тип:
string
- По умолчанию:
'nuxt.config'
- Тип:
Тайминги
setupTimeout
: Количество времени (в миллисекундах), отведенноеsetupTest
на завершение своей работы (которая может включать в себя сборку или генерацию файлов для приложения Nuxt, в зависимости от переданных параметров).- Тип:
number
- По умолчанию:
60000
- Тип:
Возможности
build
: Следует ли запускать отдельный этап сборки.- Тип:
boolean
- По умолчанию:
true
(false
, еслиbrowser
илиserver
отключены, или если указанhost
)
- Тип:
server
: Следует ли запускать сервер для ответа на запросы в наборе тестов.- Тип:
boolean
- По умолчанию:
true
(false
, если указанhost
)
- Тип:
port
: If provided, set the launched test server port to the value.- Type:
number | undefined
- Default:
undefined
- Type:
host
: Если указан, URL-адрес для использования в качестве цели тестирования вместо создания и запуска нового сервера. Полезно для выполнения "реальных" сквозных тестов на развернутой версии вашего приложения или на уже запущенном локальном сервере (что может значительно сократить время выполнения теста). См. пример сквозного теста целевого хоста ниже.- Тип:
string
- По умолчанию:
undefined
- Тип:
browser
: Под капотом для проведения тестирования в браузере Nuxt test utils используетplaywright
. Если эта опция установлена, браузер будет запущен и им можно будет управлять в последующем наборе тестов.- Тип:
boolean
- По умолчанию:
false
- Тип:
browserOptions
- Тип:
object
со следующими свойствамиtype
: Тип запускаемого браузера -Chromium
,Firefox
илиWebKit
launch
:object
опций, которые будут переданы playwright при запуске браузера. См. полный справочник API.
- Тип:
runner
: Укажите раннера для набора тестов. В настоящее время рекомендуется Vitest.- Тип:
'vitest' | 'jest' | 'cucumber'
- По умолчанию:
'vitest'
- Тип:
Пример сквозного теста целевого host
Обычным вариантом использования сквозного тестирования является запуск тестов на развернутом приложении, работающем в той же среде, которая обычно используется для продакшена.
Для локальной разработки или автоматизированных пайплайнов деплоя, тестирование на отдельном локальном сервере может быть более эффективным и обычно быстрее, чем ребилд тестовой среды между тестами.
Чтобы использовать отдельный целевой хост для сквозных тестов, просто укажите свойство host
функции setup
с нужным 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)
})
})
APIs
$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
вы можете создать настроенный экземпляр браузера в Playwright с помощью createPage
и (опционально) указать ему путь от работающего сервера. Вы можете узнать больше о доступных методах API из документации 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()
, упомянутой ранее в этом разделе.
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('Добро пожаловать в 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('Добро пожаловать в Playwright!')
})