Testowanie Vue 3 w 2026: Vitest, Vue Test Utils i pytania rekrutacyjne
Praktyczny przewodnik po testowaniu Vue w 2026: konfiguracja Vitest, montowanie komponentów z Vue Test Utils, testowanie composables i stores Pinia, mockowanie API, pomiar pokrycia oraz pytania zadawane na rozmowach kwalifikacyjnych.

Testowanie Vue oddziela pewne refaktoryzacje od kruchego zgadywania, a w 2026 roku zestaw narzędzi skonsolidował się wokół dwóch bibliotek: Vitest jako runnera oraz Vue Test Utils do montowania komponentów. Ten przewodnik pokazuje, jak skonfigurować stos technologiczny, testować komponenty i composables, mockować zależności oraz odpowiadać na pytania rekrutacyjne o testowanie Vue, które faktycznie padają podczas rozmów.
Domyślna rekomendacja z dokumentacji Vue to Vitest do testów jednostkowych i komponentowych, Vue Test Utils jako niskopoziomowe API do montowania oraz Playwright lub Cypress do testów end-to-end. Vitest wykorzystuje tę samą konfigurację Vite co aplikacja, więc testy przechodzą przez identyczny pipeline transformacji z niemal natychmiastowym przeładowaniem na gorąco.
Konfiguracja Vitest dla projektu Vue 3
Vitest współdzieli pipeline Vite, co oznacza, że jeden plik konfiguracyjny obsługuje zarówno serwer deweloperski, jak i runner testów. Opcja environment musi być ustawiona na jsdom lub happy-dom, aby testy komponentów miały DOM, w którym mogą się renderować. Flaga globals: true udostępnia describe, it i expect bez importowania ich w każdym pliku.
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)),
},
},
})W Vitest 3 powyższa konfiguracja działa bez dodatkowych ustawień po zainstalowaniu vitest, @vue/test-utils, @vitejs/plugin-vue oraz jsdom. Ponieważ alias odzwierciedla konfigurację aplikacji, importy takie jak @/composables/useCart rozwiązują się identycznie w testach i na produkcji.
Montowanie komponentów z Vue Test Utils
Vue Test Utils udostępnia dwa punkty wejścia: mount, który renderuje pełne drzewo komponentu, oraz shallowMount, który zastępuje komponenty potomne zaślepkami. W większości testów jednostkowych preferowany jest mount, ponieważ uruchamia rzeczywiste zachowanie renderowania. Zwrócony wrapper udostępnia pomocnicze metody zapytań takie jak find, get i text, które pozwalają weryfikować wyrenderowany rezultat.
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')
})
})Wskazywanie elementów poprzez atrybuty data-test zamiast klas CSS sprawia, że testy pozostają odporne: zmiana stylów w .price--sale nie zepsuje selektora, tylko rzeczywista zmiana zachowania to zrobi. To rozdzielenie odpowiedzialności powraca jako stały motyw w łatwym w utrzymaniu testowaniu Vue.
Testowanie zdarzeń użytkownika i emitowanych zdarzeń
Komponenty interaktywne wymagają asercji dotyczących tego, co dzieje się po kliknięciu lub wpisaniu tekstu. Vue Test Utils zwraca z trigger obietnicę, a jej oczekiwanie opróżnia kolejkę reaktywności Vue, dzięki czemu DOM odzwierciedla aktualizację. Pomocnik emitted rejestruje każde niestandardowe zdarzenie wyemitowane przez komponent, co pozwala weryfikować kontrakty między rodzicem a dzieckiem.
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')
})
})Vue grupuje aktualizacje reaktywne asynchronicznie. Zapomnienie o await przy wywołaniu trigger lub setValue to najczęstsza przyczyna niestabilnych testów Vue: asercje wykonują się, zanim DOM zostanie ponownie wyrenderowany, przez co odczytują nieaktualny rezultat. Gdy zmiana stanu następuje poza pomocnikiem zdarzeń, należy zaimportować nextTick z vue i wywołać await nextTick() przed asercją.
Testowanie composables Vue w izolacji
Composables, które używają wyłącznie API reaktywności (ref, computed, watch), nie potrzebują żadnego komponentu: są to zwykłe funkcje zwracające reaktywny stan, więc wystarczy wywołać je bezpośrednio i sprawdzać wartość .value. To najszybszy i najbardziej skoncentrowany sposób na pokrycie logiki biznesowej, dobrze współgrający z wzorcami opisanymi w zaawansowanych composables Vue 3.
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)
})
})Wyjątkiem są composables zależne od hooków cyklu życia, takich jak onMounted: muszą one działać wewnątrz instancji komponentu. Powszechnym obejściem jest maleńki komponent pomocniczy, który wywołuje composable w setup i udostępnia rezultat, a następnie zamontowanie tego komponentu za pomocą Vue Test Utils.
Gotowy na rozmowy o Vue.js / Nuxt.js?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Mockowanie wywołań API za pomocą vi.mock
Rzeczywiste żądania sieciowe czynią testy wolnymi i niedeterministycznymi. Vitest zastępuje moduły przez vi.mock i tworzy funkcje szpiegujące za pomocą vi.fn. Poniższy wzorzec mockuje moduł opakowujący fetch, dzięki czemu testowany composable otrzymuje kontrolowane dane, nigdy nie odwołując się do serwera.
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')
})
})Mockowanie na granicy modułu pozostawia wewnętrzną logikę composable nietkniętą, jednocześnie dając każdemu testowi pełną kontrolę nad odpowiedzią API, w tym nad ścieżkami błędów. Wywołanie mockRejectedValue pozwala pojedynczemu testowi sprawdzić, czy niepowodzenia ustawiają stan błędu i zatrzymują spinner.
Testowanie stores Pinia w 2026
Stores przechowują stan współdzielony między komponentami, a oficjalny pakiet @pinia/testing znacząco upraszcza ich testowanie. createTestingPinia domyślnie zastępuje zaślepką każdą akcję, dzięki czemu test komponentu może sprawdzić, że akcja została wywołana, bez uruchamiania jej efektów ubocznych. Przewodnik po testowaniu Pinia rekomenduje to podejście do testów na poziomie komponentów.
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()
})
})Aby testować samą logikę store, a nie integrację z komponentem, należy przekazać stubActions: false, dzięki czemu akcje wykonują się normalnie, a następnie weryfikować wynikowy stan. Ten podział na testy komponentów z zaślepionymi akcjami oraz testy store z rzeczywistymi akcjami sprawia, że każdy zestaw skupia się na jednej odpowiedzialności.
Uruchamianie testów w trybie watch i pomiar pokrycia
Vitest oferuje tryb watch, który ponownie uruchamia tylko testy dotknięte zapisanym plikiem, co skraca pętlę sprzężenia zwrotnego podczas developmentu do ułamka sekundy. Raportowanie pokrycia jest wbudowane poprzez provider @vitest/coverage-v8 i czysto integruje się z pipeline ciągłej integracji, gdzie nieudany test lub regresja pokrycia mogą zablokować scalenie.
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/**'],
},
},
})Samo uruchomienie vitest startuje lokalnie tryb watch, natomiast vitest run --coverage wykonuje pojedyncze przejście dopasowane do CI. Ustawienie jawnych progów zamienia pokrycie z metryki próżności w wymuszaną bramkę jakości: pull request, który obniża pokrycie gałęzi poniżej 75 procent, automatycznie zawodzi, co skłania współtwórców do testowania dodawanych ścieżek, a nie tylko scenariusza pozytywnego.
Najczęstsze pytania rekrutacyjne o testowanie Vue wraz z odpowiedziami
Pytania o testowanie pojawiają się na większości rozmów dla seniorów Vue, ponieważ ujawniają, jak kandydat myśli o łatwości utrzymania. Poniższe odpowiedzi obejmują pojęcia, które padają najczęściej, a pełny zestaw jest przećwiczony w module rekrutacyjnym o testowaniu Vue.
Kiedy używać shallowMount zamiast mount? Po shallowMount warto sięgnąć, gdy komponent renderuje ciężkie lub już przetestowane dzieci, a test dotyczy wyłącznie własnej logiki rodzica. Zastąpienie dzieci zaślepkami izoluje jednostkę i przyspiesza renderowanie, kosztem braku weryfikacji rzeczywistej integracji między komponentami.
Jak obsługuje się aktualizacje asynchroniczne w Vue Test Utils? Vue nakłada zmiany reaktywne w kolejnym ticku. Pomocniki zdarzeń takie jak trigger i setValue zwracają obietnice, które rozwiązują się po aktualizacji DOM, więc ich oczekiwanie wystarcza. Dla stanu zmienianego poza tymi pomocnikami wywołanie await nextTick() wymusza opróżnienie kolejki przed uruchomieniem asercji.
Jaka jest różnica między find a get? find zwraca pusty wrapper, gdy żaden element nie pasuje, i nigdy nie rzuca wyjątku, co pasuje do asercji typu expect(wrapper.find('.error').exists()).toBe(false). get rzuca natychmiast przy brakującym elemencie, co czyni go lepszym wyborem, gdy element powinien istnieć, a jego brak jest rzeczywistym błędem.
Dlaczego preferować atrybuty data-test zamiast selektorów klas? Nazwy klas zmieniają się wraz ze stylami i strukturą, więc testy, które je odpytują, psują się przy kosmetycznych edycjach. Dedykowany hak data-test wyraża intencję testową jawnie i przetrwa refaktoryzacje, ograniczając fałszywe negatywy.
Jak testować composable, który polega na onMounted? Hooki cyklu życia uruchamiają się wyłącznie wewnątrz instancji komponentu, więc composable nie może być wywołany bezpośrednio. Standardowa technika to jednorazowy komponent testowy, który wywołuje composable w setup i udostępnia jego zwracaną wartość, a następnie zamontowanie tego komponentu za pomocą Vue Test Utils i weryfikacja przez wrapper. Zachowuje to nienaruszone powiązania reaktywności i cyklu życia, jednocześnie izolując testowaną logikę. Więcej pytań koncepcyjnych i praktycznych zebrano w przewodniku najważniejsze pytania rekrutacyjne o Vue.js.
Podsumowanie
Niezawodna konfiguracja testowania Vue w 2026 sprowadza się do kilku świadomych decyzji:
- Uruchamiaj Vitest z
environment: 'jsdom'oraz wtyczką Vue, aby testy współdzieliły dokładny pipeline transformacji Vite aplikacji. - Preferuj
mountzamiastshallowMount, chyba że komponent potomny jest ciężki lub już pokryty gdzie indziej. - Zawsze oczekuj na
trigger,setValueinextTickprzed asercją, aby wyeliminować niestabilne, zależne od czasu testy. - Testuj composables używające wyłącznie API reaktywności, wywołując je bezpośrednio i sprawdzając wartość
.value. - Mockuj na granicy modułu za pomocą
vi.mock, aby zachowanie sieciowe było deterministyczne, a ścieżki błędów testowalne. - Używaj
createTestingPinia({ createSpy: vi.fn })do testów komponentów orazstubActions: false, gdy sprawdzasz logikę store. - Odpytuj elementy przez atrybuty
data-test, aby testy psuły się tylko przy rzeczywistych zmianach zachowania, nigdy przy stylach.
Opanowanie tych wzorców sprawia, że testowanie staje się narzędziem projektowym, a nie obowiązkiem, wychwytując regresje, zanim trafią na produkcję. Poznaj pełną ścieżkę nauki Vue i Nuxt, aby dalej budować umiejętności gotowe na rozmowę kwalifikacyjną.
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Tagi
Udostępnij
Powiązane artykuły

Zaawansowane Vue 3 Composables: Wzorce wielokrotnego uzytku i pytania rekrutacyjne 2026
Kompleksowy przewodnik po zaawansowanych Vue 3 Composables z wzorcami wielokrotnego uzytku, asynchroniczna obsluga bledow, Dependency Injection, walidacja formularzy i aktualne pytania rekrutacyjne na 2026 rok.

Nuxt 4 w 2026: Nowa Struktura Katalogow i Migracja z Nuxt 3
Kompletny przewodnik po Nuxt 4: nowa struktura katalogu app/, migracja krok po kroku z Nuxt 3, singleton data fetching, shallow reactivity, TypeScript context splitting, Vue Router v5, zarzadzanie meta tagami i lista kontrolna migracji.

Vue 3 Pinia vs Vuex: Nowoczesne zarządzanie stanem i pytania rekrutacyjne 2026
Porównanie Pinia i Vuex: architektura, TypeScript, Composition API, migracja, hydratacja SSR oraz najczęstsze pytania rekrutacyjne o zarządzanie stanem Vue na rok 2026.