# Pengujian Vue 3 di 2026: Vitest, Vue Test Utils, dan Pertanyaan Wawancara > Panduan praktis pengujian Vue di 2026: konfigurasi Vitest, memasang komponen dengan Vue Test Utils, menguji composable dan store Pinia, memocking API, mengukur coverage, serta pertanyaan wawancara yang diajukan tim perekrut. - Published: 2026-06-21 - Updated: 2026-07-06 - Author: SharpSkill - Tags: vue, testing, vitest, vue-test-utils, interview - Reading time: 10 min --- Pengujian Vue memisahkan refactor yang percaya diri dari tebakan yang rapuh, dan pada 2026 rantai peralatannya telah menyatu di sekitar dua pustaka: [Vitest](https://vitest.dev/guide/) sebagai runner dan [Vue Test Utils](https://test-utils.vuejs.org/) untuk memasang komponen. Panduan ini menelusuri cara mengonfigurasi tumpukan tersebut, menguji komponen dan composable, memocking dependensi, serta menjawab pertanyaan wawancara pengujian Vue yang benar-benar diajukan oleh tim perekrut. > **Tumpukan Pengujian Vue Modern di 2026** > > Rekomendasi bawaan dari [dokumentasi Vue](https://vuejs.org/guide/scaling-up/testing.html) adalah Vitest untuk pengujian unit dan komponen, Vue Test Utils sebagai API pemasangan tingkat rendah, serta Playwright atau Cypress untuk cakupan end-to-end. Vitest menggunakan kembali konfigurasi Vite yang sama dengan aplikasi, sehingga pengujian berjalan melalui pipeline transformasi yang identik dengan hot reload nyaris seketika. ## Mengonfigurasi Vitest untuk Proyek Vue 3 Vitest berbagi pipeline Vite, yang berarti satu berkas konfigurasi menggerakkan baik server pengembangan maupun test runner. Opsi `environment` harus disetel ke `jsdom` atau `happy-dom` agar pengujian komponen memiliki DOM tempat merender. Flag `globals: true` mengekspos `describe`, `it`, dan `expect` tanpa perlu mengimpornya di setiap berkas. ```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)), }, }, }) ``` Dengan Vitest 3, konfigurasi di atas langsung berfungsi setelah memasang `vitest`, `@vue/test-utils`, `@vitejs/plugin-vue`, dan `jsdom`. Karena alias mencerminkan konfigurasi aplikasi, impor seperti `@/composables/useCart` teratasi secara identik di pengujian maupun produksi. ## Memasang Komponen dengan Vue Test Utils Vue Test Utils menyediakan dua titik masuk: `mount`, yang merender seluruh pohon komponen, dan `shallowMount`, yang menggantikan komponen anak dengan stub. Untuk sebagian besar pengujian unit, `mount` lebih dianjurkan karena menguji perilaku render yang sebenarnya. Objek `wrapper` yang dikembalikan menyediakan pembantu kueri seperti `find`, `get`, dan `text` untuk melakukan asersi terhadap keluaran yang dirender. ```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') }) }) ``` Menargetkan elemen melalui atribut `data-test` alih-alih kelas CSS menjaga pengujian tetap tangguh: perubahan gaya pada `.price--sale` tidak akan merusak selektor, hanya perubahan perilaku yang sesungguhnya yang akan berpengaruh. Pemisahan ini merupakan tema yang berulang dalam pengujian Vue yang mudah dipelihara. ## Menguji Peristiwa Pengguna dan Peristiwa yang Dipancarkan Komponen interaktif membutuhkan asersi terhadap apa yang terjadi setelah klik atau input. Vue Test Utils mengembalikan sebuah promise dari `trigger`, dan menunggunya dengan `await` akan menuntaskan antrean reaktivitas Vue sehingga DOM mencerminkan pembaruan. Pembantu `emitted` merekam setiap peristiwa kustom yang dipancarkan komponen, dan inilah cara memverifikasi kontrak antara induk dan anak. ```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') }) }) ``` > **Selalu Tunggu Pembaruan DOM** > > Vue menyatukan pembaruan reaktif secara asinkron. Lupa untuk `await` pada pemanggilan `trigger` atau `setValue` adalah penyebab paling umum pengujian Vue yang tidak stabil: asersi berjalan sebelum DOM dirender ulang, sehingga membaca keluaran yang basi. Ketika perubahan status terjadi di luar pembantu peristiwa, impor `nextTick` dari `vue` lalu jalankan `await nextTick()` sebelum melakukan asersi. ## Menguji Composable Vue Secara Terisolasi Composable yang hanya menggunakan API reaktivitas (`ref`, `computed`, `watch`) sama sekali tidak memerlukan komponen: ia hanyalah fungsi biasa yang mengembalikan status reaktif, sehingga memanggilnya secara langsung dan melakukan asersi pada `.value` sudah memadai. Ini adalah cara tercepat dan paling terfokus untuk mencakup logika bisnis, dan cocok dipadukan dengan pola-pola dalam [composable Vue 3 tingkat lanjut](/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) }) }) ``` Composable yang bergantung pada hook siklus hidup seperti `onMounted` merupakan pengecualian: ia harus berjalan di dalam instansi komponen. Solusi umum adalah komponen pembantu kecil yang memanggil composable di dalam `setup` dan mengekspos hasilnya, lalu memasang pembantu tersebut dengan Vue Test Utils. ## Memocking Panggilan API dengan vi.mock Permintaan jaringan yang sungguhan membuat pengujian menjadi lambat dan tidak deterministik. Vitest mengganti modul dengan `vi.mock` serta membuat fungsi mata-mata dengan `vi.fn`. Pola di bawah ini memocking modul yang membungkus `fetch`, sehingga composable yang diuji menerima data terkendali tanpa pernah menghubungi server. ```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') }) }) ``` Memocking pada batas modul menjaga logika internal composable tetap utuh sekaligus memberi setiap pengujian kendali penuh atas respons API, termasuk jalur galat. Memanggil `mockRejectedValue` sebagai gantinya memungkinkan satu pengujian memverifikasi bahwa kegagalan menyetel status galat dan menghentikan indikator pemuatan. ## Menguji Store Pinia di 2026 Store menyimpan status yang dibagikan antarkomponen, dan paket resmi `@pinia/testing` membuatnya mudah untuk diuji. `createTestingPinia` secara bawaan mengganti setiap action dengan stub, sehingga pengujian komponen dapat memastikan sebuah action telah dikirim tanpa menjalankan efek sampingnya. [Panduan pengujian Pinia](https://pinia.vuejs.org/cookbook/testing.html) merekomendasikan pendekatan ini untuk pengujian tingkat komponen. ```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() }) }) ``` Untuk menguji logika store itu sendiri alih-alih integrasi komponen, teruskan `stubActions: false` agar action berjalan normal, lalu lakukan asersi terhadap status yang dihasilkan. Pemisahan antara pengujian komponen dengan action yang di-stub dan pengujian store dengan action nyata ini menjaga setiap suite tetap terfokus pada satu tanggung jawab. ## Menjalankan Pengujian dalam Mode Watch dan Mengukur Coverage Vitest menyertakan mode watch yang menjalankan ulang hanya pengujian yang terpengaruh oleh berkas yang disimpan, sehingga memperpendek putaran umpan balik selama pengembangan hingga sepersekian detik. Pelaporan coverage terpasang secara bawaan melalui provider `@vitest/coverage-v8` dan terintegrasi mulus ke dalam pipeline integrasi berkelanjutan, di mana pengujian yang gagal atau regresi coverage dapat memblokir penggabungan. ```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/**'], }, }, }) ``` Menjalankan `vitest` saja memulai mode watch secara lokal, sedangkan `vitest run --coverage` mengeksekusi satu kali jalan yang cocok untuk CI. Menetapkan ambang batas eksplisit mengubah coverage dari metrik pemanis menjadi gerbang mutu yang ditegakkan: sebuah pull request yang menurunkan coverage cabang di bawah 75 persen akan gagal secara otomatis, yang mendorong kontributor untuk menguji jalur yang mereka tambahkan, bukan hanya jalur bahagia. ## Pertanyaan Wawancara Pengujian Vue yang Umum Beserta Jawabannya Pertanyaan seputar pengujian muncul di hampir semua wawancara Vue tingkat senior karena mengungkap cara kandidat berpikir tentang kemudahan pemeliharaan. Jawaban di bawah ini mencakup konsep yang paling sering muncul, dan set lengkapnya dilatih dalam [modul wawancara pengujian Vue](/technologies/vue-nuxt/interview-questions/vue-testing). **Kapan `shallowMount` sebaiknya digunakan alih-alih `mount`?** Gunakan `shallowMount` ketika sebuah komponen merender anak yang berat atau yang sudah teruji dan pengujian hanya peduli pada logika induknya sendiri. Menggantikan anak dengan stub mengisolasi unit dan mempercepat render, dengan konsekuensi tidak memverifikasi integrasi nyata antarkomponen. **Bagaimana pembaruan asinkron ditangani di Vue Test Utils?** Vue menerapkan perubahan reaktif pada tick berikutnya. Pembantu peristiwa seperti `trigger` dan `setValue` mengembalikan promise yang selesai setelah DOM diperbarui, sehingga menunggunya dengan `await` sudah cukup. Untuk status yang diubah di luar pembantu tersebut, `await nextTick()` memaksa antrean tuntas sebelum asersi berjalan. **Apa perbedaan antara `find` dan `get`?** `find` mengembalikan wrapper kosong ketika tidak ada elemen yang cocok dan tidak pernah melempar galat, yang cocok untuk asersi seperti `expect(wrapper.find('.error').exists()).toBe(false)`. `get` langsung melempar galat pada elemen yang tidak ada, menjadikannya pilihan yang lebih baik ketika elemen tersebut memang diharapkan ada dan ketiadaannya merupakan kegagalan yang sesungguhnya. **Mengapa lebih memilih atribut `data-test` daripada selektor kelas?** Nama kelas berubah seiring gaya dan struktur, sehingga pengujian yang mengueri mereka akan rusak pada penyuntingan yang bersifat kosmetik. Kait `data-test` khusus mengungkapkan niat pengujian secara eksplisit dan bertahan melewati refactor, sehingga mengurangi hasil negatif palsu. **Bagaimana cara menguji composable yang bergantung pada `onMounted`?** Hook siklus hidup hanya menyala di dalam instansi komponen, sehingga composable tidak dapat dipanggil langsung. Teknik standarnya adalah komponen harness sekali pakai yang memanggil composable di dalam `setup` dan mengekspos nilai kembaliannya, lalu memasang harness tersebut dengan Vue Test Utils dan melakukan asersi melalui wrapper. Cara ini menjaga tautan reaktivitas dan siklus hidup tetap utuh sekaligus tetap mengisolasi logika yang diuji. Lebih banyak pertanyaan konseptual dan koding dikumpulkan dalam panduan [pertanyaan wawancara Vue.js esensial](/blog/vue-nuxt/essential-vuejs-interview-questions). ## Kesimpulan Penyiapan pengujian Vue yang andal di 2026 bermuara pada beberapa pilihan yang disengaja: - Jalankan Vitest dengan `environment: 'jsdom'` dan plugin Vue agar pengujian berbagi pipeline transformasi Vite yang persis sama dengan aplikasi. - Lebih memilih `mount` daripada `shallowMount` kecuali sebuah komponen anak berat atau sudah tercakup di tempat lain. - Selalu `await` pada `trigger`, `setValue`, dan `nextTick` sebelum melakukan asersi untuk menghilangkan pengujian yang tidak stabil dan bergantung pada waktu. - Uji composable yang hanya menggunakan API reaktivitas dengan memanggilnya langsung dan melakukan asersi pada `.value`. - Mocking pada batas modul dengan `vi.mock` agar perilaku jaringan deterministik dan jalur galat dapat diuji. - Gunakan `createTestingPinia({ createSpy: vi.fn })` untuk pengujian komponen dan `stubActions: false` ketika menguji logika store. - Ueri elemen melalui atribut `data-test` agar pengujian hanya rusak pada perubahan perilaku yang nyata, tidak pernah pada penataan gaya. Kuasai pola-pola ini dan pengujian akan menjadi alat perancangan alih-alih beban, menangkap regresi sebelum mencapai produksi. Jelajahi [jalur pembelajaran Vue dan Nuxt](/technologies/vue-nuxt) selengkapnya untuk terus membangun keterampilan yang siap wawancara. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/id/blog/vue-nuxt/vue-3-testing-vitest-vue-test-utils