Vue Testing (Vitest/Jest)

Write reliable tests for your Vue components

๐Ÿงช Testing Vue Apps

Testing ensures your Vue components work correctly and prevents bugs. Use Vitest for modern Vite projects or Jest for traditional setups with Vue Test Utils for component testing.


// Basic component test
import { mount } from '@vue/test-utils'
import MyButton from './MyButton.vue'

test('button displays text', () => {
  const wrapper = mount(MyButton, {
    props: { text: 'Click me' }
  })
  expect(wrapper.text()).toBe('Click me')
})
                                    

Testing Essentials

โšก

Vitest

Fast Vite-native testing

import { test, expect } from 'vitest'

test('adds numbers', () => {
  expect(1 + 1).toBe(2)
})
๐Ÿ”ง

Test Utils

Mount and test components

const wrapper = mount(Component)
wrapper.find('button').trigger('click')
๐ŸŽญ

User Events

Simulate user interactions

await wrapper.find('input')
  .setValue('test')
๐Ÿ“ธ

Snapshots

Track component output

expect(wrapper.html())
  .toMatchSnapshot()

๐Ÿ”น Setting Up Vitest

Configure Vitest for your Vue project:

# Install dependencies
npm install -D vitest @vue/test-utils jsdom
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  test: {
    globals: true,
    environment: 'jsdom'
  }
})
// package.json
{
  "scripts": {
    "test": "vitest",
    "test:ui": "vitest --ui",
    "coverage": "vitest --coverage"
  }
}

๐Ÿ”น Basic Component Testing

Test component rendering and props:

<!-- Counter.vue -->
<script setup>
import { ref } from 'vue'

const count = ref(0)
const increment = () => count.value++
</script>

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>
// Counter.test.js
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import Counter from './Counter.vue'

describe('Counter', () => {
  it('renders initial count', () => {
    const wrapper = mount(Counter)
    expect(wrapper.text()).toContain('Count: 0')
  })
  
  it('increments count on button click', async () => {
    const wrapper = mount(Counter)
    const button = wrapper.find('button')
    
    await button.trigger('click')
    expect(wrapper.text()).toContain('Count: 1')
    
    await button.trigger('click')
    expect(wrapper.text()).toContain('Count: 2')
  })
})

๐Ÿ”น Testing Props and Events

Verify component props and emitted events:

<!-- UserCard.vue -->
<script setup>
defineProps(['name', 'email'])
const emit = defineEmits(['delete'])
</script>

<template>
  <div class="user-card">
    <h3>{{ name }}</h3>
    <p>{{ email }}</p>
    <button @click="emit('delete')">Delete</button>
  </div>
</template>
// UserCard.test.js
import { mount } from '@vue/test-utils'
import UserCard from './UserCard.vue'

describe('UserCard', () => {
  it('displays user information', () => {
    const wrapper = mount(UserCard, {
      props: {
        name: 'John Doe',
        email: '[email protected]'
      }
    })
    
    expect(wrapper.text()).toContain('John Doe')
    expect(wrapper.text()).toContain('[email protected]')
  })
  
  it('emits delete event when button clicked', async () => {
    const wrapper = mount(UserCard, {
      props: { name: 'John', email: '[email protected]' }
    })
    
    await wrapper.find('button').trigger('click')
    
    expect(wrapper.emitted()).toHaveProperty('delete')
    expect(wrapper.emitted('delete')).toHaveLength(1)
  })
})

๐Ÿ”น Testing Composables

Test Vue composables and custom hooks:

// useCounter.js
import { ref } from 'vue'

export function useCounter(initial = 0) {
  const count = ref(initial)
  const increment = () => count.value++
  const decrement = () => count.value--
  const reset = () => count.value = initial
  
  return { count, increment, decrement, reset }
}
// useCounter.test.js
import { describe, it, expect } from 'vitest'
import { useCounter } from './useCounter'

describe('useCounter', () => {
  it('initializes with default value', () => {
    const { count } = useCounter()
    expect(count.value).toBe(0)
  })
  
  it('initializes with custom value', () => {
    const { count } = useCounter(10)
    expect(count.value).toBe(10)
  })
  
  it('increments count', () => {
    const { count, increment } = useCounter()
    increment()
    expect(count.value).toBe(1)
  })
  
  it('resets to initial value', () => {
    const { count, increment, reset } = useCounter(5)
    increment()
    increment()
    reset()
    expect(count.value).toBe(5)
  })
})

๐Ÿ”น Mocking and Stubs

Mock dependencies and child components:

// Component with API call
import { mount } from '@vue/test-utils'
import { vi } from 'vitest'
import UserList from './UserList.vue'

describe('UserList', () => {
  it('fetches and displays users', async () => {
    // Mock API call
    global.fetch = vi.fn(() =>
      Promise.resolve({
        json: () => Promise.resolve([
          { id: 1, name: 'John' },
          { id: 2, name: 'Jane' }
        ])
      })
    )
    
    const wrapper = mount(UserList)
    await wrapper.vm.$nextTick()
    
    expect(wrapper.text()).toContain('John')
    expect(wrapper.text()).toContain('Jane')
  })
  
  it('stubs child component', () => {
    const wrapper = mount(ParentComponent, {
      global: {
        stubs: {
          ChildComponent: true // Stub child
        }
      }
    })
    
    expect(wrapper.findComponent({ name: 'ChildComponent' }).exists()).toBe(true)
  })
})

๐Ÿ”น Testing with Pinia

Test components that use Pinia stores:

// Component.test.js
import { mount } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, it, expect } from 'vitest'
import TodoList from './TodoList.vue'
import { useTodoStore } from '@/stores/todo'

describe('TodoList', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
  })
  
  it('displays todos from store', () => {
    const wrapper = mount(TodoList, {
      global: {
        plugins: [createPinia()]
      }
    })
    
    const store = useTodoStore()
    store.todos = [
      { id: 1, text: 'Test todo', completed: false }
    ]
    
    expect(wrapper.text()).toContain('Test todo')
  })
})

๐Ÿ”น Running Tests

Execute and monitor your test suite:

# Run all tests
npm run test

# Watch mode (re-run on changes)
npm run test -- --watch

# Run specific test file
npm run test Counter.test.js

# Run with coverage report
npm run coverage

# UI mode (visual test runner)
npm run test:ui

Test Best Practices:

  • Test behavior, not implementation
  • Keep tests simple and focused
  • Use descriptive test names
  • Mock external dependencies
  • Aim for good coverage, not 100%

๐Ÿง  Test Your Knowledge

Which library is used to mount Vue components in tests?