Unit Testing
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo set up the project on your local machine, please follow the directions provided in the README.md
file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.
Though we've yet to address testing in this book, the importance of testing in front end web development can't be stressed enough.
Testing can help reveal bugs before they appear, instill confidence in your web application, and make it easy to onboard new developers on an existing codebase. As an upfront investment, testing often pays dividends over the lifetime of a system.
The development community often specify test-driven development (i.e. writing tests first then building the implementation) as the appropriate way to handle testing. Whether we employ test-driven development or build tests to validate code that has already been written, focusing on building testable code is the vital aspect to always remember.
Testing individual pieces of code that are likely to change can double or triple the amount of work it takes to keep them up. In contrast, building applications in small components and keeping large amounts of functionality broken into several methods allows us to test the functionality of a part of the larger picture. This type of code is what we mean when we say testable code.
The decision of what to test will always be up to you and your team. We'll focus on how to test your Vue applications in this chapter.
End-to-end vs. Unit Testing#
Application testing is often broken down into two main buckets: end-to-end testing or unit testing.
End-to-End Testing#
End-to-end testing is a top-down approach where tests are written to determine whether an application has been built appropriately from start to finish. We write end-to-end tests as though we are a user's movement through our application.
Though different suites can be used, Nightwatch is an end-to-end testing suite that is often used with Vue applications. Nightwatch is Node.js based and allows us to write tests that mimic how a user interacts with an application.
End-to-end tests are often labeled as integration tests since multiple modules or parts of a software system are often tested together.
Unit Testing#
Unit testing is a confined approach that involves isolating each part of an application and testing it in isolation. Tests are provided a given input and an output is often evaluated to make sure it matches expectations.
In this chapter, we'll be focusing solely on unit testing.
Testing tools#
Though numerous unit test environments/suites exist, we'll primarily use two popular tools: Mocha and Chai.
Mocha and Chai#
Mocha is a framework for writing JavaScript tests. It allows us to specify our test suites with describe
and it
blocks. We use the describe
function to segment each logical unit of tests and inside that we can use the it
function for each expectation we'd want to assert.
For instance, let's assume we wanted to test two methods, sum()
and subtract()
, in a Calculator
object. With Mocha, we'll set it up like this:
xxxxxxxxxx
describe("Calculator", () => {
it("sums 1 and 1 to 2", () => {
// assertion for the sum() method
});
it("subtracts 5 and 3 to 2", () => {
// assertion for the subtract() method
});
});
Though Mocha creates the scaffold for us to write tests, it doesn't have a built-in assertion library. For writing assertions, we'll use the Chai library.
Chai is an assertion library that can be paired with any JavaScript testing framework. Chai provides three interfaces for creating assertions:
should
expect
assert
should
and expect
assertions follow a more behavioural aspect to testing by allowing us to chain together assertions.
Since we'll be employing a behaviour-driven approach to writing tests, we'll use the expect
interface in this chapter.
Let's see how a Chai expect
assertion works. In the example given above, an expect
assertion for the sum()
method of the Calculator
object can look like this:
xxxxxxxxxx
describe("Calculator", () => {
it("sums 1 and 1 to 2", () => {
var calc = new Calculator();
expect(calc.sum(1, 1)).to.equal(2);
});
// ...
});
In the test, we're expecting that sum(1,1)
will return a value of 2
. Similarly, we can test that the subtract()
method does as intended as well:
xxxxxxxxxx
describe("Calculator", () => {
it("sums 1 and 1 to 2", () => {
var calc = new Calculator();
expect(calc.sum(1, 1)).to.equal(2);
});
it("subtracts 5 and 3 to 2", () => {
var calc = new Calculator();
expect(calc.subtract(5, 3)).to.equal(2);
});
});
Specifying a new it
block for every expectation we want to assert isn't a hard rule. On occasion, we'll write an it
block to contain several expectations.
Our Calculator
object is simple enough for us to use one describe
block for the whole class and one it
block for each method. With more complex methods that produce different outcomes, it's often suitable to have nested describe
functions: one for the object and one for each method. For example:
xxxxxxxxxx
describe('Calculator', () => {
describe('#sum', () => {
it('sums 1 and 1 to 2', () => {
});
it('called at least twice', () => {
});
}
describe('#subtract', () => {
it('subtracts 5 and 3 to 2', () => {
});
it('called only once', () => {
});
}
});
We'll be looking at a lot of describe
and it
blocks throughout this chapter which might help clear up any confusion with this setup.
For more information, be sure to check out the documentation pages for Mocha and Chai.
Testing a basic Vue component#
To understand how units tests can be made in Vue, we're going to start by testing a basic single-file Vue component.
Setup#
The example code for this entire chapter is in the testing/
folder in the code download. Within testing/
, there exists a basics/
folder that we'll be looking at first. basics/
is a Webpack configured Vue app created with the Vue CLI.
Let's cd
into testing/basics
:
xxxxxxxxxx
$ cd testing/basics
And install the necessary packages:
xxxxxxxxxx
$ npm i
If we take a look at the project directory, we'll notice the project structure mimics the Webpack configured Vue applications we’ve built throughout the book with the exception of a newly introduced tests/
folder:
xxxxxxxxxx
$ ls
README.md
babel.config.js
node_modules/
package.json
public/
src/
tests/
We'll be focusing entirely in the src/
and tests/
directories. Let's first take a look at the files within the src/
directory:
xxxxxxxxxx
$ ls src/
App.vue
main.js
We have a single component, App.vue
, and a main.js
file. The App.vue
file is the component we'll be testing. Since the component is already in it's completed state, we won't be making any edits or changes to it.
App.vue
#
When we open App.vue
, we'll see a fairly straightforward single-file component. We'll first take a look at the <template>
portion of the file:
xxxxxxxxxx
<template>
<div id="app" class="ui text container">
<div class="ui text container">
<table class="ui selectable structured large table">
<thead>
<tr>
<th>Items</th>
</tr>
</thead>
<tbody class="item-list">
<tr v-for="(item, index) in items" :key="index">
<td>{{ item }}</td>
</tr>
</tbody>
<tfoot>
<tr>
<th>
<form class="ui form" @submit="addItem">
<div class="field">
<input v-model="item"
type="text"
class="prompt"
placeholder="Add item..." />
</div>
<button type="submit"
class="ui button" :disabled="!item">Add</button>
<span @click="removeAllItems"
class="ui label">Remove all</span>
</form>
</th>
</tr>
</tfoot>
</table>
</div>
</div>
</template>
The <template>
is a <div>
element that contains an HTML table with the following details:
The table has a title of 'Items' specified in the header (
<thead>
).The body of the table,
<tbody>
, displays a list of items from anitems
array stored in the components data, with the help of thev-for
directive.The footer consists of a form that upon submit calls an
addItem()
method. In the form exists aninput
field that is bound to anitem
data property. A<button class="ui button"></button>
element is used to submit the form while a<span class="ui label"></span>
element invokes aremoveAllItems()
method on click.
Taking a look at the components <script>
section, we'll see the data values and methods that are being used in the <template>
:
xxxxxxxxxx
<script>
export default {
name: 'app',
data() {
return {
item: '',
items: []
};
},
methods: {
addItem(evt) {
evt.preventDefault();
this.items.push(this.item);
this.item = '';
},
removeAllItems() {
this.items = [];
}
}
};
</script>
item
and items
are initialized with an empty string and a blank array respectively. item
is the data property tied to the controlled input while items
is the list of items displayed in the table.
In methods
, addItem()
pushes a new item to the items
data value and clears item
. At the beginning of this method, evt.preventDefault()
is called to prevent the default browser refresh upon form submit.
The removeAllItems()
method simply sets the items
array to empty, which clears all submitted items.
<style>
consists of two simple custom CSS modifications. Like some of the chapters in this book, we're using Semantic UI as the backbone of our application styling.
main.js
#
The main.js
file imports and specifies App
as the mounting point of our application:
xxxxxxxxxx
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
Let's see the application in the browser. We'll boot the app with:
xxxxxxxxxx
$ npm run serve
And head over to http://localhost:8080
:

The app is simple. There is a field coupled with a button that adds items to a list. The "Remove all" label removes all items from the list when clicked.
tests/
#
Before we begin writing tests for the App
component, let's take a brief look at the files within the tests/
directory. The tests/
directory contains a single folder labelled unit/
:
xxxxxxxxxx
$ ls tests/
unit/
The unit/
folder hosts the spec file we'll be working from, App.spec.js
, as well as completed iterations along the way, App.1.spec.js
to App.complete.spec.js
.
xxxxxxxxxx
$ ls tests/unit/
App.spec.1.js
App.spec.2.js
App.spec.3.js
App.spec.4.js
App.spec.5.js
App.spec.complete.js
App.spec.js
In addition, a hidden .eslintrc.js
file exists within the tests/unit
folder of our application. .eslintrc.js
is a configuration file that ESLint provides to allow us to specify linting configuration for a specific directory (and its subdirectories).
The .eslintrc.js
file of this application's tests/unit
folder looks like the following:
xxxxxxxxxx
module.exports = {
env: {
mocha: true,
},
};
The env
option allows us to declare a specific environment in which our linter should be accustomed to. In the env
option, we've declared mocha: true
which predefines global variables unique for the Mocha environment. As a result, our linter will now recognize and not error with keywords such as describe
and it
.
Note: This application setup was established by scaffolding a new Vue CLI project and selecting the Mocha + Chai
unit testing solution. If we had selected Jest
as a testing framework, we would see a very similar but slightly different initial scaffold.
Testing App
#
In package.json
, we have a test
script that runs the tests located within the App.spec.js
file. We currently have a dummy test set up for us in App.spec.js
. Let's execute our Mocha test runner from inside testing/basics
and see what happens:
xxxxxxxxxx
$ npm run test

After the steps our runner takes to boot up, we can see information on the describe
and it
blocks that were run for the dummy test. In addition, we're given a summary of the overall test status at the end:

A separate script in package.json
, test:watch
, allows us to run the tests in the App.spec.js
file in watch mode:
xxxxxxxxxx
"test:watch": "vue-cli-service test:unit tests/unit/App.spec.js --watch",
In this mode, our runner does not quit after the test suite finishes. Instead, it watches the whole file for changes. When a change is detected, it re-runs the test suite.
To execute tests from the App.spec.js
file in watch mode, we can run the following command in our terminal:
xxxxxxxxxx
$ npm run test:watch
Throughout this chapter, we’ll continue to mention to execute the test suite with
npm run test
. However, you can just keep a console window open with the tests running in watch mode if you’d like.The Vue CLI project was scaffolded with the
npm run test:unit
script, which is the script that runs the unit tests for all test files in our project. We've introduced the other two scripts (npm run test
andnpm run test: watch
) which contain additional options to give us the ability to run tests only for theApp.spec.js
file and to be able to do so in watch mode.
Writing our first spec#
Let's take a look at the App.spec.js
file and replace the existing dummy test with something more useful.
If we open App.spec.js
, we'll see that it's currently laid out like this:
xxxxxxxxxx
import { expect } from 'chai';
describe('App.vue', () => {
it('should run this dummy test', () => {
expect('Dummy' + ' Test!').to.equal('Dummy Test!');
});
});
In the first line, we're importing the expect
assertion from chai
. Taking a look at our test, we can see we've titled our describe
block after the module under test, App.vue
. Let's remove the dummy test and create an actual test.
For our first spec, we'll assert that the application should render the correct expected content:
Test initial data#
We'll introduce a new spec that's responsible in asserting the component sets the correct default data:
xxxxxxxxxx
describe("App.vue", () => {
it("should set correct default data", () => {
// our assertion will go here
});
});
The term "spec" is often used in JavaScript unit testing to refer to the specification (i.e. details of a feature) that must be fulfilled.
Since we'll be testing the App
component, we'll need to import it at the beginning of our test file. Though we can import App
using the relative file path destination:
This lesson preview is part of the Fullstack Vue course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.

Get unlimited access to Fullstack Vue, plus 0+ \newline books, guides and courses with the \newline Pro subscription.
