Unit Testing

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To 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:

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:

  1. should

  2. expect

  3. 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:

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:

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:

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:

And install the necessary packages:

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:

We'll be focusing entirely in the src/ and tests/ directories. Let's first take a look at the files within the src/ directory:

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:

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 an items array stored in the components data, with the help of the v-for directive.

  • The footer consists of a form that upon submit calls an addItem() method. In the form exists an input field that is bound to an item data property. A <button class="ui button"></button> element is used to submit the form while a <span class="ui label"></span> element invokes a removeAllItems() 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>:

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:

Let's see the application in the browser. We'll boot the app with:

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/:

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.

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:

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:

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:

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:

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 and npm run test: watch) which contain additional options to give us the ability to run tests only for the App.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:

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:

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.

    Screenshot
    Unlock This Course

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

    Thumbnail for the \newline course Fullstack Vue