On this page
Testing Vue
Test Vue components from the user’s perspective — verify rendered output and interactions, not implementation details.
Setup with Vitest
npm install -D vitest @vue/test-utils jsdom @testing-library/vue
vite.config.js:
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
test: {
environment: 'jsdom',
},
});
package.json:
{ "scripts": { "test": "vitest" } }
Basic Component Test
// components/Counter.test.js
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import Counter from './Counter.vue';
describe('Counter', () => {
it('renders initial count', () => {
const wrapper = mount(Counter);
expect(wrapper.text()).toContain('Count: 0');
});
it('increments on button click', async () => {
const wrapper = mount(Counter);
await wrapper.find('button').trigger('click');
expect(wrapper.text()).toContain('Count: 1');
});
});
Counter component:
<script setup>
import { ref } from 'vue';
const count = ref(0);
</script>
<template>
<button @click="count++">Count: {{ count }}</button>
</template>
Testing Props and Emits
import { mount } from '@vue/test-utils';
import UserCard from './UserCard.vue';
it('displays user name from props', () => {
const wrapper = mount(UserCard, {
props: { name: 'Alice', email: '[email protected]' },
});
expect(wrapper.text()).toContain('Alice');
});
it('emits delete event', async () => {
const wrapper = mount(UserCard, { props: { name: 'Bob' } });
await wrapper.find('[data-testid="delete"]').trigger('click');
expect(wrapper.emitted('delete')).toHaveLength(1);
});
Testing Async Components
import { mount, flushPromises } from '@vue/test-utils';
import PostList from './PostList.vue';
it('loads and displays posts', async () => {
global.fetch = vi.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve([{ id: 1, title: 'Hello' }]),
})
);
const wrapper = mount(PostList);
expect(wrapper.text()).toContain('Loading');
await flushPromises();
expect(wrapper.text()).toContain('Hello');
});
Testing with Pinia
import { mount } from '@vue/test-utils';
import { createPinia, setActivePinia } from 'pinia';
import CartSummary from './CartSummary.vue';
it('shows cart total', () => {
setActivePinia(createPinia());
const wrapper = mount(CartSummary);
expect(wrapper.text()).toContain('Total: $0');
});
What NOT to Test
- Internal ref variable names
- Third-party library internals
Run Tests
npm test # Watch mode
npm test -- --run # Single run
npm test -- --coverage
Write tests that confirm your UI works for users.