# Тестування Vue 3 у 2026 році: Vitest, Vue Test Utils та питання співбесід > Практичний посібник із тестування Vue у 2026 році: налаштування Vitest, монтування компонентів через Vue Test Utils, перевірка композаблів і сховищ Pinia, мокування API, вимірювання покриття та питання, які ставлять команди на співбесідах. - Published: 2026-06-21 - Updated: 2026-07-06 - Author: SharpSkill - Tags: vue, testing, vitest, vue-test-utils, interview - Reading time: 10 min --- Тестування Vue відділяє впевнений рефакторинг від крихких здогадок, і у 2026 році інструментарій сконцентрувався навколо двох бібліотек: [Vitest](https://vitest.dev/guide/) як тестового раннера та [Vue Test Utils](https://test-utils.vuejs.org/) для монтування компонентів. Цей посібник послідовно розглядає налаштування стека, тестування компонентів і композаблів, мокування залежностей, а також відповіді на ті питання про тестування Vue, які команди справді ставлять на співбесідах. > **Сучасний стек тестування Vue у 2026 році** > > Стандартна рекомендація з [документації Vue](https://vuejs.org/guide/scaling-up/testing.html) — це Vitest для юніт- та компонентних тестів, Vue Test Utils як низькорівневий API монтування, а також Playwright або Cypress для наскрізних (end-to-end) сценаріїв. Vitest перевикористовує ту саму конфігурацію Vite, що й застосунок, тож тести проходять через ідентичний конвеєр трансформації з майже миттєвим гарячим перезавантаженням. ## Налаштування Vitest для проєкту Vue 3 Vitest використовує спільний із Vite конвеєр, а отже один конфігураційний файл керує і сервером розробки, і тестовим раннером. Параметр `environment` має бути встановлений у `jsdom` або `happy-dom`, щоб компонентні тести мали DOM, у який можна рендерити. Прапорець `globals: true` робить `describe`, `it` та `expect` доступними без імпорту в кожному файлі. ```typescript // vitest.config.ts import { defineConfig } from 'vitest/config' import vue from '@vitejs/plugin-vue' import { fileURLToPath } from 'node:url' export default defineConfig({ plugins: [vue()], test: { // jsdom provides document/window so components can mount environment: 'jsdom', // expose describe/it/expect globally globals: true, // run this before each test file (matchers, cleanup) setupFiles: ['./tests/setup.ts'], // only pick up *.spec.ts and *.test.ts files include: ['**/*.{test,spec}.ts'], }, resolve: { alias: { // mirror the @ alias used across the app '@': fileURLToPath(new URL('./src', import.meta.url)), }, }, }) ``` У Vitest 3 наведена конфігурація працює без додаткових зусиль після встановлення `vitest`, `@vue/test-utils`, `@vitejs/plugin-vue` та `jsdom`. Оскільки аліас дублює конфігурацію застосунку, імпорти на кшталт `@/composables/useCart` розв'язуються однаково і в тестах, і в продакшені. ## Монтування компонентів через Vue Test Utils Vue Test Utils надає дві точки входу: `mount`, що рендерить повне дерево компонента, та `shallowMount`, що замінює дочірні компоненти заглушками. Для більшості юніт-тестів перевагу має `mount`, бо він задіює реальну поведінку рендерингу. Повернутий `wrapper` надає допоміжні методи запитів на зразок `find`, `get` та `text` для перевірки відрендереного результату. ```typescript // PriceTag.spec.ts import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import PriceTag from '@/components/PriceTag.vue' describe('PriceTag', () => { it('formats the amount as currency', () => { const wrapper = mount(PriceTag, { // props are passed through the mounting options props: { amount: 4200, currency: 'EUR' }, }) // get() throws if the selector is missing, unlike find() expect(wrapper.get('[data-test="price"]').text()).toBe('€42.00') }) it('applies a discount class when on sale', () => { const wrapper = mount(PriceTag, { props: { amount: 4200, currency: 'EUR', onSale: true }, }) // classes() returns the array of applied CSS classes expect(wrapper.get('[data-test="price"]').classes()).toContain('price--sale') }) }) ``` Орієнтація на елементи через атрибути `data-test`, а не через CSS-класи, робить тести стійкими: зміна стилів у `.price--sale` не зламає селектор, це зробить лише справжня зміна поведінки. Таке розчеплення є наскрізною темою підтримуваного тестування Vue. ## Тестування подій користувача та еміттованих подій Інтерактивні компоненти потребують перевірок того, що відбувається після кліку або введення. Vue Test Utils повертає проміс із виклику `trigger`, і його очікування скидає чергу реактивності Vue, щоб DOM відобразив оновлення. Допоміжний метод `emitted` записує кожну кастомну подію, яку викликав компонент, і саме так перевіряються контракти між батьківським і дочірнім компонентами. ```typescript // SearchBar.spec.ts import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import SearchBar from '@/components/SearchBar.vue' describe('SearchBar', () => { it('emits search with the typed query', async () => { const wrapper = mount(SearchBar) // setValue writes to the input and fires an input event await wrapper.get('input').setValue('vitest') // awaiting trigger flushes the reactivity queue before assertions await wrapper.get('[data-test="submit"]').trigger('click') // emitted() returns a record of every event and its payloads const events = wrapper.emitted('search') expect(events).toHaveLength(1) // events[0] is the first emission, [0] its first argument expect(events![0][0]).toBe('vitest') }) }) ``` > **Завжди очікуйте оновлення DOM** > > Vue групує реактивні оновлення асинхронно. Забутий `await` перед викликом `trigger` чи `setValue` — це найпоширеніша причина нестабільних тестів Vue: перевірки виконуються до того, як DOM перерендериться, тож вони читають застарілий результат. Коли зміна стану відбувається поза допоміжним методом для подій, слід імпортувати `nextTick` із `vue` і виконати `await nextTick()` перед перевіркою. ## Тестування композаблів Vue в ізоляції Композабли, які використовують лише API реактивності (`ref`, `computed`, `watch`), не потребують жодного компонента: це звичайні функції, що повертають реактивний стан, тож достатньо викликати їх напряму й перевіряти `.value`. Це найшвидший і найточніший спосіб покрити бізнес-логіку, і він добре поєднується з підходами з [просунутих композаблів Vue 3](/blog/vue-nuxt/advanced-vue-3-composables-reusable-patterns). ```typescript // useCart.spec.ts import { describe, it, expect } from 'vitest' import { useCart } from '@/composables/useCart' describe('useCart', () => { it('tracks total price as items change', () => { // composables using only ref/computed run without mounting const { items, total, addItem } = useCart() expect(total.value).toBe(0) addItem({ id: 1, price: 1500, qty: 2 }) // computed total recalculates synchronously on state change expect(total.value).toBe(3000) addItem({ id: 2, price: 500, qty: 1 }) expect(items.value).toHaveLength(2) expect(total.value).toBe(3500) }) }) ``` Винятком є композабли, що залежать від хуків життєвого циклу на кшталт `onMounted`: вони мають виконуватися всередині екземпляра компонента. Поширений обхідний прийом — крихітний допоміжний компонент, який викликає композабл у `setup` і надає результат назовні, після чого цей помічник монтується через Vue Test Utils. ## Мокування викликів API через vi.mock Справжні мережеві запити роблять тести повільними й недетермінованими. Vitest замінює модулі за допомогою `vi.mock` і створює шпигунські функції через `vi.fn`. Наведений нижче патерн мокує модуль-обгортку навколо `fetch`, тож композабл, який тестується, отримує контрольовані дані, жодного разу не звертаючись до сервера. ```typescript // useProducts.spec.ts import { describe, it, expect, vi, beforeEach } from 'vitest' import { useProducts } from '@/composables/useProducts' import { getProducts } from '@/api/products' // replace the whole module with mocked exports vi.mock('@/api/products', () => ({ getProducts: vi.fn(), })) describe('useProducts', () => { beforeEach(() => { // reset call history between tests to avoid leakage vi.mocked(getProducts).mockReset() }) it('exposes fetched products and clears loading', async () => { // define what the mocked API returns for this test vi.mocked(getProducts).mockResolvedValue([ { id: 1, name: 'Keyboard' }, ]) const { products, loading, load } = useProducts() await load() expect(getProducts).toHaveBeenCalledOnce() expect(loading.value).toBe(false) expect(products.value[0].name).toBe('Keyboard') }) }) ``` Мокування на межі модуля залишає внутрішню логіку композабла недоторканою, водночас даючи кожному тесту повний контроль над відповіддю API, включно з гілками помилок. Виклик `mockRejectedValue` натомість дозволяє окремому тесту перевірити, що збій встановлює стан помилки і зупиняє індикатор завантаження. ## Тестування сховищ Pinia у 2026 році Сховища тримають стан, спільний для кількох компонентів, і офіційний пакет `@pinia/testing` робить їх зручними для тестування. `createTestingPinia` за замовчуванням замінює кожну дію заглушкою, тож компонентний тест може перевірити, що дію було відправлено, не запускаючи її побічних ефектів. [Посібник із тестування Pinia](https://pinia.vuejs.org/cookbook/testing.html) рекомендує саме такий підхід для тестів рівня компонента. ```typescript // CheckoutButton.spec.ts import { describe, it, expect, vi } from 'vitest' import { mount } from '@vue/test-utils' import { createTestingPinia } from '@pinia/testing' import CheckoutButton from '@/components/CheckoutButton.vue' import { useCartStore } from '@/stores/cart' describe('CheckoutButton', () => { it('dispatches checkout on click', async () => { const wrapper = mount(CheckoutButton, { global: { plugins: [ // vi.fn spies on actions instead of executing them createTestingPinia({ createSpy: vi.fn }), ], }, }) const store = useCartStore() await wrapper.get('[data-test="checkout"]').trigger('click') // the action is stubbed, so only the dispatch is asserted expect(store.checkout).toHaveBeenCalledOnce() }) }) ``` Для тестування самої логіки сховища, а не інтеграції з компонентом, слід передати `stubActions: false`, щоб дії виконувалися нормально, а потім перевіряти отриманий стан. Такий поділ на компонентні тести із заглушеними діями та тести сховища зі справжніми діями тримає кожен набір тестів сфокусованим на одній відповідальності. ## Запуск тестів у режимі спостереження та вимірювання покриття Vitest постачається з режимом спостереження, який перезапускає лише ті тести, на які впливає збережений файл, і це скорочує цикл зворотного зв'язку під час розробки до частки секунди. Звітування про покриття вбудоване через провайдер `@vitest/coverage-v8` і чисто інтегрується в конвеєр безперервної інтеграції, де провалений тест або регресія покриття можуть заблокувати злиття. ```typescript // vitest.config.ts (coverage section) export default defineConfig({ test: { coverage: { // v8 is the fastest provider and needs no instrumentation step provider: 'v8', reporter: ['text', 'html', 'lcov'], // fail CI when a threshold drops below the target thresholds: { statements: 80, branches: 75, functions: 80, }, // exclude generated and config files from the report exclude: ['**/*.config.ts', '**/types/**'], }, }, }) ``` Запуск самого лише `vitest` стартує режим спостереження локально, тоді як `vitest run --coverage` виконує єдиний прохід, придатний для CI. Встановлення явних порогів перетворює покриття з показника заради марнославства на примусову межу якості: пул-реквест, що опускає покриття гілок нижче 75 відсотків, провалюється автоматично, а це підштовхує учасників тестувати ті шляхи, які вони додають, а не лише щасливий сценарій. ## Поширені питання співбесід про тестування Vue та відповіді Питання про тестування виринають на більшості співбесід для сеньйор-рівня Vue, бо вони розкривають, як кандидат мислить про підтримуваність коду. Відповіді нижче охоплюють концепції, що трапляються найчастіше, а повний набір відпрацьовується в [модулі співбесід із тестування Vue](/technologies/vue-nuxt/interview-questions/vue-testing). **Коли слід використовувати `shallowMount` замість `mount`?** Варто звернутися до `shallowMount`, коли компонент рендерить важкі або вже протестовані дочірні елементи, а тест цікавить лише власна логіка батьківського компонента. Заміна дочірніх елементів заглушками ізолює юніт і пришвидшує рендеринг ціною того, що реальна інтеграція між компонентами не перевіряється. **Як обробляються асинхронні оновлення у Vue Test Utils?** Vue застосовує реактивні зміни на наступному тіку. Допоміжні методи для подій, такі як `trigger` і `setValue`, повертають проміси, що вирішуються після оновлення DOM, тож їхнього очікування достатньо. Для стану, зміненого поза цими методами, `await nextTick()` примушує чергу скинутися перед виконанням перевірок. **Яка різниця між `find` і `get`?** `find` повертає порожній wrapper, коли жоден елемент не збігається, і ніколи не кидає виняток, що підходить для перевірок на кшталт `expect(wrapper.find('.error').exists()).toBe(false)`. `get` негайно кидає виняток на відсутньому елементі, що робить його кращим вибором, коли елемент має існувати, а його відсутність є справжнім збоєм. **Чому варто надавати перевагу атрибутам `data-test`, а не селекторам за класами?** Імена класів змінюються разом зі стилями та структурою, тож тести, що звертаються до них, ламаються на косметичних правках. Виділений гачок `data-test` явно виражає намір тестування і переживає рефакторинги, зменшуючи кількість хибних спрацювань. **Як тестується композабл, що покладається на `onMounted`?** Хуки життєвого циклу спрацьовують лише всередині екземпляра компонента, тож композабл не можна викликати напряму. Стандартна техніка — одноразовий допоміжний компонент, який викликає композабл у `setup` і надає його повернуте значення назовні, після чого цей помічник монтується через Vue Test Utils, а перевірки виконуються через wrapper. Це зберігає прив'язку реактивності й життєвого циклу цілою, водночас ізолюючи логіку, яка тестується. Більше концептуальних питань і завдань на кодування зібрано в посібнику [ключові питання співбесід із Vue.js](/blog/vue-nuxt/essential-vuejs-interview-questions). ## Висновок Надійне налаштування тестування Vue у 2026 році зводиться до кількох свідомих рішень: - Запускати Vitest з `environment: 'jsdom'` і плагіном Vue, щоб тести використовували точно той самий конвеєр трансформації Vite, що й застосунок. - Надавати перевагу `mount` над `shallowMount`, якщо тільки дочірній компонент не є важким або вже покритим деінде. - Завжди очікувати `trigger`, `setValue` та `nextTick` перед перевіркою, щоб усунути нестабільні, залежні від часу тести. - Тестувати композабли, що використовують лише API реактивності, викликаючи їх напряму й перевіряючи `.value`. - Мокувати на межі модуля через `vi.mock`, щоб мережева поведінка була детермінованою, а гілки помилок піддавалися тестуванню. - Використовувати `createTestingPinia({ createSpy: vi.fn })` для компонентних тестів і `stubActions: false`, коли задіюється логіка сховища. - Звертатися до елементів через атрибути `data-test`, щоб тести ламалися лише на справжніх змінах поведінки, а не на стилях. З опануванням цих патернів тестування стає радше інструментом проєктування, ніж рутиною, ловлячи регресії ще до того, як вони дістануться продакшену. Досліджуйте повний [навчальний шлях Vue та Nuxt](/technologies/vue-nuxt), щоб і далі розвивати навички, готові до співбесід. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/vue-nuxt/vue-3-testing-vitest-vue-test-utils