# 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. - Published: 2026-06-21 - Updated: 2026-07-06 - Author: SharpSkill - Tags: vue, testing, vitest, vue-test-utils, interview - Reading time: 10 min --- Testowanie Vue oddziela pewne refaktoryzacje od kruchego zgadywania, a w 2026 roku zestaw narzędzi skonsolidował się wokół dwóch bibliotek: [Vitest](https://vitest.dev/guide/) jako runnera oraz [Vue Test Utils](https://test-utils.vuejs.org/) 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. > **Nowoczesny zestaw do testowania Vue w 2026** > > Domyślna rekomendacja z [dokumentacji Vue](https://vuejs.org/guide/scaling-up/testing.html) 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. ```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)), }, }, }) ``` 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. ```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') }) }) ``` 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. ```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') }) }) ``` > **Zawsze oczekuj na aktualizacje DOM** > > 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](/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) }) }) ``` 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. ## 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. ```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') }) }) ``` 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](https://pinia.vuejs.org/cookbook/testing.html) rekomenduje to podejście do testów na poziomie komponentów. ```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() }) }) ``` 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. ```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/**'], }, }, }) ``` 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](/technologies/vue-nuxt/interview-questions/vue-testing). **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](/blog/vue-nuxt/essential-vuejs-interview-questions). ## 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 `mount` zamiast `shallowMount`, chyba że komponent potomny jest ciężki lub już pokryty gdzie indziej. - Zawsze oczekuj na `trigger`, `setValue` i `nextTick` przed 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 oraz `stubActions: 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](/technologies/vue-nuxt), aby dalej budować umiejętności gotowe na rozmowę kwalifikacyjną. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/vue-nuxt/vue-3-testing-vitest-vue-test-utils