We use cookies to enhance your experience on the site
CodeWorlds

Testing Vue Components

Welcome to the NOVA LAB Launch Platform! Year 2087 - before sending systems to Mars, every piece of code must pass rigorous testing. Just as rocket systems are checked before launch, we test our Vue applications before deployment.

Vitest - Testing Framework

Vitest is a modern testing framework for Vue:

  • Fast like Vite
  • Compatible with Jest
  • Native ESM
  • TypeScript out-of-the-box

Installation

1npm install -D vitest @vue/test-utils happy-dom

Configuration

1// vitest.config.js
2import { defineConfig } from 'vitest/config'
3import vue from '@vitejs/plugin-vue'
4
5export default defineConfig({
6  plugins: [vue()],
7  test: {
8    globals: true,
9    environment: 'happy-dom'
10  }
11})
1// package.json
2{
3  "scripts": {
4    "test": "vitest",
5    "test:ui": "vitest --ui",
6    "test:coverage": "vitest --coverage"
7  }
8}

First Test

1// components/Counter.spec.js
2import { describe, it, expect } from 'vitest'
3import { mount } from '@vue/test-utils'
4import Counter from './Counter.vue'
5
6describe('Counter', () => {
7  it('renders count', () => {
8    const wrapper = mount(Counter)
9    expect(wrapper.text()).toContain('Count: 0')
10  })
11
12  it('increments count on button click', async () => {
13    const wrapper = mount(Counter)
14
15    await wrapper.find('button.increment').trigger('click')
16
17    expect(wrapper.text()).toContain('Count: 1')
18  })
19
20  it('accepts initial count prop', () => {
21    const wrapper = mount(Counter, {
22      props: {
23        initialCount: 10
24      }
25    })
26
27    expect(wrapper.text()).toContain('Count: 10')
28  })
29})

Component Under Test

1<!-- Counter.vue -->
2<template>
3  <div class="counter">
4    <p>Count: {{ count }}</p>
5    <button class="increment" @click="count++">+</button>
6    <button class="decrement" @click="count--">-</button>
7  </div>
8</template>
9
10<script setup>
11import { ref } from 'vue'
12
13const props = defineProps({
14  initialCount: {
15    type: Number,
16    default: 0
17  }
18})
19
20const count = ref(props.initialCount)
21</script>

Testing Events

1import { mount } from '@vue/test-utils'
2import ArtworkCard from './ArtworkCard.vue'
3
4describe('ArtworkCard', () => {
5  it('emits select event on click', async () => {
6    const artwork = {
7      id: 1,
8      title: 'Mona Lisa',
9      artist: 'Leonardo'
10    }
11
12    const wrapper = mount(ArtworkCard, {
13      props: { artwork }
14    })
15
16    await wrapper.trigger('click')
17
18    expect(wrapper.emitted()).toHaveProperty('select')
19    expect(wrapper.emitted('select')[0]).toEqual([artwork])
20  })
21
22  it('emits favorite event on heart click', async () => {
23    const wrapper = mount(ArtworkCard, {
24      props: {
25        artwork: { id: 1, title: 'Test' }
26      }
27    })
28
29    await wrapper.find('.favorite-btn').trigger('click')
30
31    expect(wrapper.emitted('favorite')).toBeTruthy()
32  })
33})

Testing Slots

1import { mount } from '@vue/test-utils'
2import BaseCard from './BaseCard.vue'
3
4describe('BaseCard', () => {
5  it('renders default slot', () => {
6    const wrapper = mount(BaseCard, {
7      slots: {
8        default: '<p>Test content</p>'
9      }
10    })
11
12    expect(wrapper.html()).toContain('Test content')
13  })
14
15  it('renders named slots', () => {
16    const wrapper = mount(BaseCard, {
17      slots: {
18        header: '<h2>Header</h2>',
19        default: '<p>Body</p>',
20        footer: '<footer>Footer</footer>'
21      }
22    })
23
24    expect(wrapper.find('h2').text()).toBe('Header')
25    expect(wrapper.find('p').text()).toBe('Body')
26    expect(wrapper.find('footer').text()).toBe('Footer')
27  })
28})
Go to CodeWorlds