How to Write A Unit Test for Vue.js

Lily Rae

Vue.js is a JavaScript framework which is useful for building the front-end of web applications, particularly when creating complex features. For every project, it's necessary to go through everything in our applications and check that it all works as expected. However, for large projects, it quickly becomes tedious to check every feature after every new update. For this reason, we can create automated tests which can run all the time and give us reassurance that our code works. In this tutorial, we will create a few simple unit tests for VueJS which show how to get started.

To have something to test, we will be making a basic to-do list component. We'll test that the list gets displayed correctly and also that the user can add new items to the to-do list. Hopefully, by the end of this tutorial, you'll be able to write tests which check what your component outputs to the user and which simulate user actions by interacting with the HTML (for example by clicking a button).

Here is my Github repo with all the code in this post.

Set up

Setting up a JavaScript project can be a complicated process. There are so many libraries which need to be chosen, all of them serving a slightly different purpose. Fortunately, for VueJS, we have vue-cli, which sets everything up for us! You'll need to have npm installed first and then you can run the following commands:

npm install -g vue-cli
vue init webpack project-name

At this stage, you'll be prompted with a few questions. Most of them are straightforward and you can choose the default option, the only requirement is that you answer YES to including vue-router and YES to setting up unit tests with Karma and Mocha. Then install the dependencies:

cd project-name
npm install

This final command will start up your browser and take you to localhost where your application will be running:

npm run dev

Here is a (very) brief overview of some of the key dependencies that vue-cli has set up for us, including the version which was installed for my own project.

Dependencies

Webpack (v2.3) is a bundler, which combines the various JavaScript, CSS, HTML (and other) files and makes them ready to be handled by the client.
Babel (v6.22) is a compiler for ECMAScript 6 to ECMAScript 5. These are different JavaScript standards and currently browsers are not able to understand all of ECMAScript 6 and so it needs to be converted.

Testing Dependencies

Karma (v1.4) is a test runner, which spawns a web server with your project's application code inside and executes tests against it.
Mocha (v3.2) is a testing framework for JavaScript.
Chai (v3.5) is an assertion library which can be used with Mocha.



In your project, you should find the following folders: build, config, node_modules, src, static and test. The only ones which are important for this tutorial are src, which will hold our application code, and test.

My First Test

It is always good to start off with something fairly basic to get going. We'll start off with creating our simple list component. Create a new file called List.vue in the src/components folder and put the following code inside:

<template>
  <div>
    <h1>My To Do List</h1>
    </br>
    <!--displays list -->
    <ul>
      <li v-for="item in listItems">{{ item }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'list',
  data () {
    return {
      listItems: ['buy food', 'play games', 'sleep'],
    }
  }
}
</script>

In the component, the list items are stored in an array (listItems) in the component data. This data is then accessible to the template where it is looped over in a foreach loop (v-for), and displayed on the page.

Since it's more fun if we can see our list, we can create a new route to display our component. Go into src/router/index.js and add a route, and your code should look something like this:

import Vue from 'vue'
import Router from 'vue-router'
import Hello from '@/components/Hello'
import List from '@/components/List'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Hello',
      component: Hello
    },
    {
      path: '/to-do',
      name: 'ToDo',
      component: List
    },
  ]
})

Now if you navigate to localhost:8080/#/to-do, you will see your list!

First we want to test if the data is displayed correctly. Inside test/unit/specs, create a new file List.spec.js and put the following code:

import List from '@/components/List';
import Vue from 'vue';

describe('List.vue', () => {

  it('displays items from the list', () => {
      // our test goes here
  })
})

In this file, we are describing the List.vue component and we have a single empty test which will check that it (the component) displays items from the list. This is the basic file structure for Mocha tests.

Inside our test, we first need to set up the Vue component. Copy the code below and put it under the comment 'our test goes here':

// build component
const Constructor = Vue.extend(List);
const ListComponent = new Constructor().$mount();

We extend Vue and then mount our component. It is important to mount the component as this is the bit that renders the HTML in our template. This essentially means that the HTML gets built and the variables in our template (e.g. {{ item }}) get filled with data, enabling us to later have access to the HTML (via $el).

With our component ready, we can write our first assertion. In this example we are using the 'expect' style, which is provided by Chai assertion library, along with 'should' and 'assert'. Put the following code after mounting the component:

// assert that component text contains items from the list
expect(ListComponent.$el.textContent).to.contain('play games');

As I mentioned before, we can get the component's HTML using ListComponent.$el, and to access only the inner HTML (i.e. the text), we have ListComponent.$el.textContent. The assertion is checking that the text contains one of the list items which is set in the component's data.

To check that everything is working as it should be, we can run the tests! With the vue-cli project, we can simply type npm run unit, which is an alias for cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run (much more memorable!)

npm run unit

If all the tests have passed, it will be showing green with a list of the successful tests and a code coverage report, letting you know what percentage of your application code was executed during the tests.

Simulating User Input

That's a good start, but it is rare indeed that your app will only be displaying data. The next feature we're going to add is for the user to be able to add new items to their list. For this we want an input box where the user can type the new item and a button which adds the item to the list on click. This is an updated version of List.vue:

<template>
  <div>
    <h1>My To Do List</h1>
    </br>
    <input v-model="newItem" >
    <button @click="addItemToList">Add</button>
    <!-- displays list --> 
    <ul>
      <li v-for="item in listItems">{{ item }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'test',
  data () {
    return {
      listItems: ['buy food', 'play games', 'sleep'],
      newItem: ''
    }
  },
  methods: {
      addItemToList() {
        this.listItems.push(this.newItem);
        this.newItem = '';
      }
  }
}
</script>

Using v-model, the value of the input box is bound to newItem variable stored in the component's data. When the button is clicked, the function addItemToList gets executed which adds the newItem to the list array and clears the newItem so that more can be added to the list.

To start testing this feature, create a new empty test in List.spec.js and add the test code as we go along:

it('adds a new item to list on click', () => {
    // our test goes here
})

First we'll want to build our component and mimic a user typing in the input box. Since VueJS binds the value of the input box to the newItem variable, we can simply set our value to newItem.

// build component
const Constructor = Vue.extend(List);
const ListComponent = new Constructor().$mount();

// set value of new item
ListComponent.newItem = 'brush my teeth';

Next we need to click the button. We have to find the button in the HTML, which is available with $el. Only this time we can use querySelector so as to find the actual element. It is possible to find an element using its class (.buttonClass), its id (#buttonId) or the element's name (button).

// find button
const button = ListComponent.$el.querySelector('button');

To simulate a click, we need to pass the button a new event object to dispatch. In the testing environment, the List component won't be listening for any events and so we need to manually run the watcher.

// simulate click event
const clickEvent = new window.Event('click');
button.dispatchEvent(clickEvent);
ListComponent._watcher.run();

Finally we need to check that the newItem gets displayed, which we already know how to do from the first test! We might also want to check that the newItem gets stored in the list array.

//assert list contains new item
expect(ListComponent.$el.textContent).to.contain('brush my teeth');
expect(ListComponent.listItems).to.contain('brush my teeth');

Here is the test file in full:

import List from '@/components/List';
import Vue from 'vue';

describe('List.vue', () => {
  it('displays items from the list', () => {
    const Constructor = Vue.extend(List);
    const ListComponent = new Constructor().$mount();
    expect(ListComponent.$el.textContent).to.contain('play games');
  })

  it('adds a new item to list on click', () => {
    // build component
    const Constructor = Vue.extend(List);
    const ListComponent = new Constructor().$mount();

    // set input value
    ListComponent.newItem = 'brush my teeth';

    // simulate click event
    const button = ListComponent.$el.querySelector('button');
    const clickEvent = new window.Event('click');
    button.dispatchEvent(clickEvent);
    ListComponent._watcher.run();

    // assert list contains new item
    expect(ListComponent.$el.textContent).to.contain('brush my teeth');
    expect(ListComponent.listItems).to.contain('brush my teeth');
  })
})

Now we can run our tests again, and they should show green!

Hopefully this code will be clear to you now, but it is not particularly readable, especially for someone making VueJS tests for the first time. There is a VueJS utility library which tucks away some of the more complicated looking code. To use it, we can go to our project root and run the following:

npm install avoriaz

This test works exactly the same way as the one before except now we can hide the setup of the Vue component behind mount() and, for clicking the button, all we need is two lines of code: find() the button and dispatch() the click event.

import { mount } from 'avoriaz';
import List from '@/components/List';
import Vue from 'vue';

describe('List.vue', () => {
  // previous tests ..

  it('adds new item to list on click with avoriaz', () => {
       // build component
    const ListComponent = mount(List);

    // set input value
    ListComponent.setData({
      newItem: 'brush my teeth',
    });

    // simulate click event
    const button = ListComponent.find('button')[0];
    button.dispatch('click');

    // assert list contains new item
    expect(ListComponent.text()).to.contain('brush my teeth');
    expect(ListComponent.data().listItems).to.contain('brush my teeth');
  })
})

Conclusion

I personally find writing tests so essential to my normal work flow and yet with JavaScript, and VueJS in particular, I had some trouble getting started. Hopefully this tutorial will help out anyone in the same position as me!

Here is my Github repo with the code from this tutorial.

Lily Rae

2 posts

Biologist turned Software Developer. Trying to conquer all things.