Skip to main content
Vue.js for Beginners to Advanced
CHAPTER 26 Beginner

Testing in Vue.js

Updated: May 18, 2026
5 min read

# CHAPTER 26

Testing in Vue.js

1. Chapter Introduction

Testing ensures your Vue components work correctly across changes. Vitest is the official test runner for Vite-based projects — fast, Vue-friendly, and Jest-compatible. Vue Test Utils provides the component mounting and interaction API.

2. Learning Objectives

  • Set up Vitest and Vue Test Utils.
  • Write unit tests for components.
  • Test reactive state, props, and emits.
  • Test composables.
  • Test Pinia stores.

3. Setup

bash
1
npm install -D vitest @vue/test-utils @testing-library/vue jsdom
vite.config.js
1234567891011
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  test: {
    environment: 'jsdom',   // Simulate browser DOM
    globals: true,           // Auto-import describe/it/expect
    setupFiles: './src/test/setup.js'
  }
})
package.json
1234567
{
  "scripts": {
    "test": "vitest",
    "test:ui": "vitest --ui",
    "coverage": "vitest run --coverage"
  }
}

4. Testing Components

javascript
1234567891011121314151617181920212223242526272829303132333435
// src/components/Counter.vue
// <script setup>
// const count = ref(0)
// </script>
// <template>
//   <button @click="count++" data-testid="increment-btn">Count: {{ count }}</button>
// </template>

// tests/Counter.spec.js
import { describe, it, expect } from &#039;vitest&#039;
import { mount } from &#039;@vue/test-utils&#039;
import Counter from &#039;@/components/Counter.vue&#039;

describe(&#039;Counter Component&#039;, () => {
  it(&#039;renders initial count of 0&#039;, () => {
    const wrapper = mount(Counter)
    expect(wrapper.text()).toContain(&#039;Count: 0&#039;)
  })

  it(&#039;increments count when button is clicked&#039;, async () => {
    const wrapper = mount(Counter)
    const button = wrapper.find(&#039;[data-testid="increment-btn"]&#039;)
    await button.trigger(&#039;click&#039;)
    expect(wrapper.text()).toContain(&#039;Count: 1&#039;)
  })

  it(&#039;increments 3 times correctly&#039;, async () => {
    const wrapper = mount(Counter)
    const button = wrapper.find(&#039;button&#039;)
    await button.trigger(&#039;click&#039;)
    await button.trigger(&#039;click&#039;)
    await button.trigger(&#039;click&#039;)
    expect(wrapper.text()).toContain(&#039;Count: 3&#039;)
  })
})

5. Testing Props and Emits

javascript
123456789101112131415161718192021222324252627282930
// tests/ProductCard.spec.js
import { describe, it, expect, vi } from &#039;vitest&#039;
import { mount } from &#039;@vue/test-utils&#039;
import ProductCard from &#039;@/components/ProductCard.vue&#039;

describe(&#039;ProductCard&#039;, () => {
  const product = { id: 1, name: &#039;MacBook Pro&#039;, price: 1999 }

  it(&#039;renders product name and price&#039;, () => {
    const wrapper = mount(ProductCard, {
      props: { product }
    })
    expect(wrapper.text()).toContain(&#039;MacBook Pro&#039;)
    expect(wrapper.text()).toContain(&#039;1999&#039;)
  })

  it(&#039;emits add-to-cart event with product when clicked&#039;, async () => {
    const wrapper = mount(ProductCard, { props: { product } })
    await wrapper.find(&#039;[data-testid="add-btn"]&#039;).trigger(&#039;click&#039;)
    expect(wrapper.emitted(&#039;add-to-cart&#039;)).toBeTruthy()
    expect(wrapper.emitted(&#039;add-to-cart&#039;)[0][0]).toEqual(product)
  })

  it(&#039;shows out-of-stock badge when inStock is false&#039;, () => {
    const wrapper = mount(ProductCard, {
      props: { product, inStock: false }
    })
    expect(wrapper.find(&#039;.oos-badge&#039;).exists()).toBe(true)
  })
})

6. Testing Composables

javascript
1234567891011121314151617181920212223242526272829303132333435
// tests/useCounter.spec.js
import { describe, it, expect } from &#039;vitest&#039;
import { useCounter } from &#039;@/composables/useCounter&#039;

describe(&#039;useCounter composable&#039;, () => {
  it(&#039;initializes with default value 0&#039;, () => {
    const { count } = useCounter()
    expect(count.value).toBe(0)
  })

  it(&#039;initializes with custom value&#039;, () => {
    const { count } = useCounter(10)
    expect(count.value).toBe(10)
  })

  it(&#039;increments correctly&#039;, () => {
    const { count, increment } = useCounter(5)
    increment()
    expect(count.value).toBe(6)
  })

  it(&#039;respects max boundary&#039;, () => {
    const { count, increment } = useCounter(10, { max: 10 })
    increment()
    expect(count.value).toBe(10)  // Should not exceed max
  })

  it(&#039;resets to initial value&#039;, () => {
    const { count, increment, reset } = useCounter(0)
    increment()
    increment()
    reset()
    expect(count.value).toBe(0)
  })
})

7. Testing Pinia Stores

javascript
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
// tests/cart.store.spec.js
import { describe, it, expect, beforeEach } from &#039;vitest&#039;
import { setActivePinia, createPinia } from &#039;pinia&#039;
import { useCartStore } from &#039;@/stores/cart&#039;

describe(&#039;Cart Store&#039;, () => {
  beforeEach(() => {
    setActivePinia(createPinia())
  })

  const product = { id: 1, name: &#039;Vue Course&#039;, price: 49 }

  it(&#039;starts empty&#039;, () => {
    const cart = useCartStore()
    expect(cart.items).toHaveLength(0)
    expect(cart.isEmpty).toBe(true)
  })

  it(&#039;adds item correctly&#039;, () => {
    const cart = useCartStore()
    cart.addItem(product)
    expect(cart.items).toHaveLength(1)
    expect(cart.itemCount).toBe(1)
  })

  it(&#039;increments quantity for duplicate items&#039;, () => {
    const cart = useCartStore()
    cart.addItem(product)
    cart.addItem(product)
    expect(cart.items).toHaveLength(1)
    expect(cart.items[0].quantity).toBe(2)
  })

  it(&#039;calculates total correctly&#039;, () => {
    const cart = useCartStore()
    cart.addItem(product)
    cart.addItem(product)
    expect(cart.subtotal).toBe(98)
  })

  it(&#039;removes item&#039;, () => {
    const cart = useCartStore()
    cart.addItem(product)
    cart.removeItem(product.id)
    expect(cart.isEmpty).toBe(true)
  })
})

8. Common Mistakes

  • Not calling setActivePinia(createPinia()) before each Pinia test: Without this, Pinia stores are shared across tests, causing state bleed-through.
  • Not using await with trigger(): DOM updates after events are async. Always await trigger('click') before asserting.

9. MCQs

Question 1

Test runner for Vite Vue projects?

Question 2

mount() from Vue Test Utils does?

Question 3

wrapper.emitted('event') checks?

Question 4

trigger('click') needs await because?

Question 5

Testing composables requires?

Question 6

setActivePinia(createPinia()) in beforeEach ensures?

Question 7

data-testid attributes on elements?

Question 8

vi.fn() creates?

Question 9

Coverage report shows?

Question 10

wrapper.find('.class').exists() returns?

10. Interview Questions

  • Q: How do you test a Vue component that emits events?
  • Q: What is the difference between Vitest and Jest?

11. Summary

Vitest + Vue Test Utils creates a fast, Vue-native testing workflow. Component tests verify rendering, props, and events. Composable tests call the function directly — no mounting required. Pinia store tests use fresh Pinia instances per test for isolation.

12. Next Chapter Recommendation

In Chapter 27: Vue Interview Preparation, we compile 50 interview questions with detailed answers and 20 coding challenges covering the full Vue ecosystem.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·