# Testing Vue Components

> A practical approach to automatically verifying Vue component UI behavior: writing real-world tests with Vue Test Utils.

- Published: 2019-10-06
- Category: Tools & Technologies
- Tags: Vue.js, Testing
- Reading time: 3 min read
- Source: https://www.muhammetsafak.com.tr/en/blog/testing-vue-components/
- Language: en-US
- Author: Muhammet Şafak

---
I had already developed a solid habit of [writing tests with PHPUnit](/en/blog/writing-my-first-tests-with-phpunit/) on the PHP side. But when it came to frontend components, I kept putting testing off. I told myself "testing UIs is hard" — which is partly true, but laziness played a role in that delay. Now that I'm using Vue Test Utils properly, I can see that testing Vue components is far more practical than I ever imagined.

In this post I'm going to skip the "why should you write tests?" question and jump straight into "how do you do it?"

## The toolset

The standard test combination for Vue 2: **Vue Test Utils** + **Jest**. Vue Test Utils is the official helper library that lets you render [components](/en/blog/transitioning-to-component-based-ui-architecture-with-vue/), interact with them, and query their output. Jest is the test runner and assertion library.

Installation:

```bash
npm install --save-dev @vue/test-utils jest vue-jest babel-jest
```

You need to transform Vue files with `vue-jest` inside `jest.config.js`.

## Your first component test

Let's take a simple `Counter.vue` component:

```vue
<template>
  <div>
    <span data-testid="count">{{ count }}</span>
    <button @click="increment">Artır</button>
  </div>
</template>

<script>
export default {
  data() {
    return { count: 0 };
  },
  methods: {
    increment() {
      this.count++;
    }
  }
};
</script>
```

And its test:

```javascript
import { shallowMount } from '@vue/test-utils';
import Counter from '@/components/Counter.vue';

describe('Counter', () => {
  it('başlangıçta 0 gösterir', () => {
    const wrapper = shallowMount(Counter);
    expect(wrapper.find('[data-testid="count"]').text()).toBe('0');
  });

  it('butona tıklayınca değer artar', async () => {
    const wrapper = shallowMount(Counter);
    await wrapper.find('button').trigger('click');
    expect(wrapper.find('[data-testid="count"]').text()).toBe('1');
  });
});
```

`shallowMount` renders the component shallowly: child components are replaced with stubs. `mount` renders the full tree. For isolation purposes, `shallowMount` is usually preferable.

Using `data-testid` attributes has become a habit of mine. CSS classes and element selectors break tests when they change; `data-testid` exists solely for testing and stays stable.

## Testing props and emits

Testing how a component behaves based on its props, and which events it emits:

```javascript
import { shallowMount } from '@vue/test-utils';
import ConfirmDialog from '@/components/ConfirmDialog.vue';

describe('ConfirmDialog', () => {
  it('message prop\'unu gösterir', () => {
    const wrapper = shallowMount(ConfirmDialog, {
      propsData: { message: 'Silmek istediğinizden emin misiniz?' }
    });
    expect(wrapper.text()).toContain('Silmek istediğinizden emin misiniz?');
  });

  it('onayla butonuna tıklayınca confirm eventi yayar', async () => {
    const wrapper = shallowMount(ConfirmDialog, {
      propsData: { message: 'Test' }
    });
    await wrapper.find('[data-testid="confirm-btn"]').trigger('click');
    expect(wrapper.emitted('confirm')).toBeTruthy();
  });
});
```

Emit tests feel a bit strange coming from the backend, because you're not testing the receiver of the event — you're simply checking "was it emitted?" But this is the right approach: the component's responsibility is to emit, and the parent component decides what to do with it. Keep the scope tight and the test stays clear.

## Testing components with a Vuex store

Components connected to a store require a bit more setup. There are two approaches: creating a real store or mocking the store. For isolated unit tests, I prefer mocking:

```javascript
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import UserList from '@/components/UserList.vue';

const localVue = createLocalVue();
localVue.use(Vuex);

describe('UserList', () => {
  let store;

  beforeEach(() => {
    store = new Vuex.Store({
      state: {
        users: [
          { id: 1, name: 'Ali' },
          { id: 2, name: 'Ayşe' }
        ]
      }
    });
  });

  it('store\'daki kullanıcıları listeler', () => {
    const wrapper = shallowMount(UserList, { store, localVue });
    expect(wrapper.findAll('[data-testid="user-item"]')).toHaveLength(2);
  });
});
```

One thing I'm careful about here: recreating the store inside `beforeEach` for every test. A shared store can let one test contaminate another. Just like starting with a clean database in PHPUnit, every test here starts with a clean store.

## What to test, and what not to?

You don't need to test every line. Valuable tests are:

- How the component responds to user input.
- How rendering changes based on props.
- Critical conditional branches (conditional rendering).
- Emitted events.

Testing CSS styles or animations is generally not worth the cost. Test the component's *behavior*, not its appearance.

On the backend you test "does this endpoint return 200 or 403?"; on the frontend you test "when I click this button, does the right thing happen?" The frame is different, the logic is the same.

Writing frontend tests should become as ingrained a habit as writing backend tests. Clicking through a component manually and calling it "working" is not enough — especially when a component depends on another and you're about to make changes. The first time I refactored with tests in place and watched that safety net catch things, I was genuinely surprised it had taken me so long.

There's another thing I noticed: when you write a test, you're looking at the component from the outside — what are its props, what events does it emit, what does the user see? That perspective forces you to think about how the component will be used from the outside. Well-written test scenarios point toward a well-designed component interface. The clarity that testing demands also improves the design. If a component becomes hard to test, that's almost always a sign that it's taken on too many responsibilities; making the test easier by breaking down the component improves both the test scenario and the actual code.
