How to unit test Vue components for beginners

In this tutorial I’ll teach you what unit tests are, why you should write them, and how to write unit tests for Vue components.

Note: for this tutorial you need to have node > 6 installed, if it’s not download it here. We also use ES6 syntax, so you should brush up on that if you’re not familiar with it

What are unit tests?

Unit tests are a way to automatically check that code does what it’s intended to do. You run your source code in a controlled environment and assert that it produce the expected output.

For example, here is a sum function:

function sum (a, b) {
  return a + b
}

You could write a testSum function that throws an error if sum does not return the sum of a and b:

function testSum () {
  if (add(1, 2) !== 3) {
    throw new Error('add should return sum of both parameters')
  }
}

Now you can run testSum to check that sum works correctly. If sum(1, 2) does not return 3, testSum will throw an error to alert you that the sum function is broken.

At their simplest, this is what unit tests are. Functions that check the result of the function you are testing.

Why write unit tests?

Now you know what unit tests are, you’re probably wondering why you should use them.

The benefits of unit tests are:

  • They tell you that your code is behaving correctly
  • They provide documentation for code

In large projects there can be thousands of unit tests. With thousands of unit tests you can refactor the codebase, run your test command and be confident that nothing is broken.

Now you know what unit tests are, and why you should write them. The next step is to learn how.

How to unit test vue components

Unit tests for Vue components are different to normal unit tests. Instead of calling a function and checking it returns the correct result, you mount a component, interact with it, and assert that it responds correctly. You’ll see what that looks like later on, first you need to do some set up.

Getting started

Create a new directory with the following structure:

 .
 ├── src
 | └── components
 └── package.json

The components directory will hold your components and tests.

Open the command line and change your current directory to the root of the project. (Note: If you don’t know how to change your working directory, check out this stackoverflow answer for linux/mac and this guide for windows).

When you’re in the directory, bootstrap an npm project with the following command:

npm init -y

Before you add any tests, you need to learn about Mocha.

Mocha

Mocha is a test framework. You define tests in a file and run them from the command line with mocha. Mocha detects tests that throw errors and reports them to you.

You define a test for mocha by using the it function:

it('returns sum of parameters a and b', () => {
  if (sum(1, 2) !== 3) {
    throw new Error('add should return sum of both parameters')
  }
})

You can group related tests in a describe block to improve test organization:

describe('sum', () => {
  it('returns sum of parameters a and b', () => {
    // ..
  })
})

This is great so far, but those if statements aren’t very expressive. That’s where Chai comes in.

Chai

Chai is an assertion library. It asserts that a condition is true and throws an error if it does not.

Using Chai, the sum test function becomes:

describe('sum', () => {
  it('returns sum of parameters a and b', () => {
    expect(add(1, 2)).to.equal(3)
  })
})

Much more readable.

Now you’re ready use Chai and Mocha write your first test sanity test. A sanity test is a passing test that you write to make sure the test system is set up correctly.

In the command line, run:

npm install --save-dev mocha chai

When that’s installed, add a new file src/components/Foo.spec.js and add the following code:

const expect = require('chai').expect

describe('first test', () => {
  it('is true', () => {
    expect(true).to.equal(true)
  })
})

Now you’re going to create an npm script to run the tests. Open package.json, and replace the script field with the following test script:

Edit the test script to look like this:

// package.json
{
  // ..
  "scripts": {
    "test": "mocha src/**/*.spec.js"
  },
  // ..
}

This command runs mocha and tells it to look in all directories for any file with a .spec.js extension.

Now, from the command line type:

npm test

If everything is set up correctly, you will see your first passing unit test.

Now that you’ve got a sanity test written, it’s time to write tests for a Vue component.

Unit testing Vue components

As I said, Vue components are different to test than normal functions. To test a Vue component you’ll mount a component in memory, interact with it, and make sure it responds correctly.

In the components directory, create a file named Foo.vue (src/components/Foo.vue). Add the following code to Foo.vue:

<template>
  <div class="foo">
    <h1></h1>
    <button id="change-message" v-on:click="changeMessage">Change message</button>
	  <p></p>
  </div>
</template>

<script>
export default {
  name: 'hello',
  data() {
    return {
      msg: 'Welcome to Your Vue.js App'
    };
  },
  props: ['passedProp'],
  methods: {
    changeMessage() {
      this.msg = 'new message'
    }
  }
}
</script>

Something to note about single-file components (SFCs) is that you can’t run them in node. You need to compile them to JavaScript first. To do that, you can use webpack and Vue Loader.

As well as webpack and Vue Loader, there are a few other dependencies to add. In the command line, enter:

npm install --save vue &&
npm install --save-dev @vue/test-utils babel-loader @babel/core chai mocha mocha-webpack vue-loader@12 vue-template-compiler webpack@3

*Note this examples uses Webpack 3 and Vue Loader 12 because mocha-webpack Woo boy. That’s a lot of dependencies! If you want to know what they do, read the wall of text below!

@vue/test-utils is a vue test utilities library. babel-loader is a webpack loader to transpile JS with babel. @babel/core is used by babel-loader to transpile. css-loader is a dependency of vue-loader, mocha-webpack compiles files with webpack before running them in mocha. vue-loader is a plugin for webpack that compiles Vue files. vue-template-compiler is a dependency of vue-loader that compiles vue templates into JavaScript. webpack is a build tool that bundles code.

Don’t worry if you didn’t read or understand that. Most of these libraries are used to compile the code.

In this tutorial, webpack is doing the heavy lifting. webpack is a module bundler that takes multiple JavaScript files (modules) and compiles them into a single JavaScript file. It can also process files, and you’ll use it to process Vue SFCs into JavaScript.

In the root directory create a file named webpack.config.js, and add the following code:

const path = require('path')

module.exports = {
  module: {
    loaders: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.js$/,
        loader: 'babel-loader'
      }
    ]
  }
}

Now in package.json, edit the test script to use mocha-webpack:

// package.json
{
  // ..
  "scripts": {
    "test": "mocha-webpack src/**/*.spec.js"
  },
  // ..
}

mocha-webpack compiles code with webpack before running the compiled code in mocha.

Run npm run test to make sure everything is working.

After that setup, it’s finally time to write a test for a single-file component. Open Foo.spec.js and replace the existing code with the code below:

// src/components/Foo.spec.js

import { expect } from 'chai'
import { mount } from '@vue/test-utils'
import Foo from './Foo.vue'

describe('Foo.vue', () => {
  it('has a root element with class foo', () => {
    const wrapper = mount(Foo)
    expect(wrapper.is('.foo')).to.equal(true)
  })
})

This test asserts that the root element of Foo has the className foo.

Now run npm run test. You will see an error - [vue-test-utils]: window is undefined.

Vue Test Utils is the library that exports the mount method, and it must be run in a browser environment. You could run the tests in a real browser like Chrome, but opening and closing a browser adds a lot of overhead to tests. Instead, you can use JSDOM to create and run a JavaScript DOM environment in Node.

In the command line, run:

npm install --save-dev jsdom jsdom-global

Now add a file named test-setup.js to the root directory, and paste in the following code:

// test-setup.js
require('jsdom-global')()

This function creates a window object and adds it to the Node global object. You need to run it before you run the tests that use Vue Test Utils. To do that you need to edit your test script again:

// package.json
{
  // ..
  "scripts": {
    "test": "mocha-webpack src/**/*.spec.js --require test-setup"
  },
  // ..
}

Now mocha will run the test-setup file before running the tests.

If you run npm run test, you should have a passing test. Hooray!

Time for some more tests.

Currently you have a test that checks that the wrapper is rendering a div with the class foo:

const wrapper = mount(Foo)
expect(wrapper.is('.foo')).to.equal(true)

You’re using the mount method imported from Vue Test Utils, and then calling `isz on the wrapper.

Under the hood, mount mounts the Vue component and creates a wrapper class with methods to test the Vue component. Above you use the is method, but there are loads more to choose from. Have a look through the docs for the full list.

A useful method is find. find searches the wrapper for elements matching a selector and returns a new wrapper of the first matching node:

const wrapper = mount(Foo)
const p = wrapper.find('p')

Here, p will be the first <p> tag found in Foo.

You can also pass props to mounted components when you call mount. This is done with the propsData option:

const wrapper = mount(Foo, {
  propsData: {
    propertyA: 'property'
  }
})

Using find and propsData, you can test whether Foo correctly renders passedProp in the <p> tag:

it('sets the prop value', () => {
  const passedProp = 'some text'
  const wrapper = mount(Foo, {
    propsData: { passedProp }
  })
  const p = wrapper.find('p')
  expect(p.text()).to.equal(passedProp)
})

Cool! Let’s look at one last method - trigger.

trigger dispatches a DOM event to the wrapper root element. In the Foo component, you have a button with id change-message that should change the text of the <h1> tag when clicked. Write a test to check that it does:

it('changes h1 text when #change-text is clicked', () => {
  const wrapper = mount(Foo)
  const changeMessage = wrapper.find('#change-message')
  changeMessage.trigger('click')
  const h1 = wrapper.find('h1')
  expect(h1.text()).to.equal('new message')
})

This test finds the changeMessage button, dispatches a click event and then asserts that the <h1> text has changed. Success!

All together now:

// src/components/Foo.spec.js

import { expect } from 'chai'
import { mount } from '@vue/test-utils'
import Foo from './Foo.vue'

describe('Foo.vue', () => {
  it('has a root element with class foo', () => {
    const wrapper = mount(Foo)
    expect(wrapper.is('.foo')).to.equal(true)
  })

  it('sets the prop value', () => {
    const passedProp = 'some text'
    const wrapper = mount(Foo, {
      propsData: { passedProp }
    })
    const p = wrapper.find('p')
    expect(p.text()).to.equal(passedProp)
  })

  it('changes h1 text when #change-text is clicked', () => {
    const wrapper = mount(Foo)
    const changeMessage = wrapper.find('#change-message')
    changeMessage.trigger('click')
    const h1 = wrapper.find('h1')
    expect(h1.text()).to.equal('new message')
  })
})

That’s all the tests we’re going to write in this tutorial. To learn more about unit testing Vue components, see How to unit test Vue components.

A repo for this tutorial is available here.

Want more? I’ve written a book on Testing Vue applications. You can buy it now as part of the eartly release program. The book covers every aspect of testing Vue applications.