Test Driven Development: How to test using Vue.js, VueTestUtils and Jest

Tags: vue, testdev, javascript, productivity
Published: 2021-10-12 01:06:58

<!-- **Table of content** 1\. Prerequisites 2\. Objectives 3\. Introduction to Test Driven Development - Unit Testing - 4\. Introduction to Node, Vue.js, VueTestUtils 5\. Setting up our environment 6\. Learning 7\. Conclusion -->

## Introduction

Test Driven Development is a software practice. TDD focuses on three (3) important things:

- Testing
- Coding
- Refactoring.

The goal of TDD is to ensure developers have a roadmap of their code outcome before writing the actual code. In TDD, you write a test (mostly unit tests). The test is expected to fail because there is no corresponding code. After writing your test, you are required to write your code or script. After, you can continuously refractor your codebase to successfully pass all your test cases. The test process is the driving force of software development. It helps build a resilient and optimized coding structure over a long time. TDD ensures developers write only the necessary codes required for a software or a codebase. TDD helps reduce breakage in applications while in production mode and improved productivity.

**_Unit Testing_** 
TDD requires that you write unit tests often. Unit is mostly referred to as class or group of functions. Keeping your unit minimal is a good practice in TDD. Minimal units help in reducing debugging period. In a component-based app like Vue.js, the unit is a component.

To read more on Test Driven Development, get [Test Driven Development: By Example](http://www.amazon.fr/dp/0321146530) by Kent Beck

**_Introduction to Node.js, Vue.js, VueTestUtils_**

**_Vue.js_** 
Vue.js is a progressive framework for building user interfaces. Learning Vue.js requires an intermediate knowledge of HTML, CSS and Javascript. Grasping the basics before going into the framework might be the best decision in any chosen language of your choice. Vue.js is a javascript framework. For an introduction to Vue.js syntax, you can check out this [Helloworld example](https://codepen.io/team/Vue/pen/KKpRVpx) by the Vue.js team. Vue.js is a component-based framework.

**_Node.js_** 
Node.js is an open-source project that runs the V8 javascript engine, it is also a cross-platform runtime environment. Node.js has helped developers write server-side code. Node.js uses the javascript syntax. With a vast module of libraries, developers have shorter development time as most of the libraries handle bulky code contents. Node.js also have frameworks like [Express](https://expressjs.com/), [Fastify](https://fastify.io/), [FeatherJs](https://feathersjs.com/), [Socket.io](https://socket.io/), [Strapi](https://strapi.io/) and others.

**_Vue Test Utils_** 
How do we test our components? Earlier, we introduced units and for component-based apps, units are components. Vue.js is a component-based app needs components to be isolated to allow for testing.Vue test utils help with the isolation. Vue Test Utils is an official library of helper functions to help users test their Vue.js components. It provides some methods to mount and interact with Vue.js components in an isolated manner. We refer to this as a wrapper.

_But what is a wrapper?_ 
A wrapper is an abstraction of the mounted component. It provides some utility functions such as when users want to trigger a click or an event. We'll use this to execute some input ( props, store changes, etc.) so we can check that the output is correct (component rendering, Vue events, function calls, etc.).

## Prerequisites

For this tutorial, you're required to have:

1. [Node.js](https://nodejs.dev/learn/introduction-to-nodejs) installed.
2. Also, we will be using [Vue3](https://v3.vuejs.org/guide/introduction.html) for this tutorial
3. [Vue test utils 2](https://vue-test-utils.vuejs.org/) (Vue test utils 1 target and earlier versions)
4. A code editor.

**Objectives**

- Learn the basic principles of Test Driven Development
- Why you should test your Vue.js app
- Learn how to unit test a Vue.js app.

**Setting up our environment**

Vue3 gives us the opportunity to select unit tests while creating a vue project. You can follow the steps below for the [manual installation](https://vue-test-utils.vuejs.org/installation/#using-vue-test-utils-with-jest-recommended).

For existing projects, you can use Vue CLI to set up Vue Test Utils in your current Vue app.

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

Your `package.json` file should have added a new command.

```json
[package.json]
{
  "scripts": {
    "test:unit": "vue-cli-service test:unit"
  }
}
```

After installation of all relevant dependecies either manually or to existing projects, we proceed to our code editor.

## Step 1 -- Setting up our files

After opening our code in our code editor, we will go to the test directory. Test directory is a root folder in our `<project-name>`. Open the unit folder, then you can create a new file (`<project-name>/tests/unit/<file-name.spec.js>`). It is a good practice to name the file as the component. Initially, there is an `example.spec.js` file in the unit folder. Remember the goal of TDD is testing before code. You will create a boilerplate for the vue component in the component folder (`<project-name>/src/component/loginpage.vue`). The boilerplate structureis provided below.

```html
[<project-name>/src/component/loginpage.vue]

<template>
    <div></div>
</template>

<script>
    export default {

    }
</script>

<style scoped>

</style>
```

In our spec file, we are importing our vue component and making use of the Vue test utils.

```bash
import{ shallowMount } from '@vue/test-utils'
import Login from '@/components/Login'
```

## Step 2 -- First test

Our first test is to ensure if our Login components displays a form.

```javascript
[<project-name>/tests/unit/loginpage.spec.js]

import { shallowMount } from '@vue/test-utils'
import Login from '@/components/Login'

describe('login.vue', () => {
    test('should show the form element on the user output', () => {
      const wrapper = shallowMount(Login)
      expect(wrapper.find("form").exists()).toBe(true)
    }),
})
```

Running our test using the `yarn test:unit --watch` or `npm run test:unit` command, our test failed!

```bash
FAIL  tests/unit/loginpage.spec.js
 login.vue
   ✕ should show the form element on the screen (13ms)

 ● login.vue › should show the form element on the screen
 Cannot call isVisible on an empty DOMWrapper.

   Expected: true
   Received: false

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        1.174s
Ran all test suites related to changed files.
```

Notice the error? Cannot call `isVisible` on an empty DOMWrapper. We wrote a test without the code it will act on. Our component booilerplate is empty. To resolve this, we simply go to boilerplate our and write this code.

```html
[<project-name>/src/component/loginpage.vue]

<template>
    <div>
      <form action="">
      </form>
    </div>
</template>

<script>
    export default {

    }
</script>

<style scoped>

</style>
```

Our test should pass now. Congratulations! You just wrote your first successful test!

```bash
PASS  tests/unit/loginpage.spec.js
 login.vue
   ✓ should show the form element on the screen (60ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.273s, estimated 9s
Ran all test suites related to changed files.

Watch Usage: Press w to show more.
```

## Step 3 -- Further testing

Let's go further by checking if our input field exists.

```javascript
[<project-name>/tests/unit/loginpage.spec.js]

test('should contain input fields', () => {
    const wrapper = shallowMount(Login)
    expect(wrapper.find('form > input').exists()).toBe(true)
})

test('form should contain input fields with type="text"', () => {
    const wrapper = shallowMount(Login)
    expect(wrapper.get('input[type=tjavascriptext]').exists()).toBe(true)
})
```

Our test failed as there was no input field present in our form element.

```bash
FAIL  tests/unit/loginpage.spec.js
 login.vue
   ✓ should show the form element on the screen (10ms)
   ✕ should contain input fields (5ms)
   ✕ form should contain input fields with type="text" (10ms)

 ● login.vue › should contain input fields

   expect(received).toBe(expected) // Object.is equality

   Expected: true
   Received: false

 ● login.vue › form should contain input fields with type="text"

   Unable to get input[type=text] within: <div>


Test Suites: 1 failed, 1 total
Tests:       2 failed, 1 passed, 3 total
Snapshots:   0 total
Time:        3.549s
Ran all test suites related to changed files.
```

Now let's open our `Login component` and add some codes.

```html
[<project-name>/src/component/loginpage.vue]

<template>
    <div>
        <form action="">
            <input type="text" name="" id="username" placeholder="Username">
        </form>
    </div>
</template>
```

Our test passed!

```bash
PASS  tests/unit/loginpage.spec.js
 login.vue
   ✓ should show the form element on the screen (13ms)
   ✓ should contain input fields (2ms)
   ✓ form should contain input fields with type="text" (2ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        1.805s, estimated 2s
Ran all test suites related to changed files.
```

A bonus test is confirming the attribute of our input field. The `get()` function allows for parameters. We can check for tag attributes like `type=text`. isVisible check the visibility status (showing on the user output device). Though `isVisible()` is deprecated, latest release of Vue still accept it.

Our last test! Testing if our button triggers a click event. We trigger the click event listener, so that the Component executes the submit method. We use await to make sure the action is being reflected by Vue.

```javascript
[<project-name>/tests/unit/loginpage.spec.js]

test('button trigger event', async () => {
    await wrapper.find('form > button').trigger('click')
    expect(wrapper.emitted()).toHaveProperty('submit')
})
```

We have a failed test again.

```bash
FAIL  tests/unit/loginpage.spec.js
 login.vue
   ✓ should show the form element on the screen (12ms)
   ✓ should contain input fields (3ms)
   ✓ form should contain input fields with type="text" (1ms)
   ✕ button trigger event (4ms)

 ● login.vue › button trigger event

   Cannot call trigger on an empty DOMWrapper.

Test Suites: 1 failed, 1 total
Tests:       1 failed, 3 passed, 4 total
Snapshots:   0 total
Time:        3s
Ran all test suites related to changed files.
```

Our triggering test failed as we don't have a corresponding button element in our login component. In our login component, we are going to add the button element.

```html
[<project-name>/src/component/loginpage.vue]

<template>
    <div>
        <form action="">
            <input type="text" name="" id="username" placeholder="Username">

            <button @click="submit">Submit</button>
        </form>
    </div>
</template>
```

Our test failed as we do not have a corresponding method in our component boilerplate.

```html
[<project-name>/src/component/loginpage.vue]

<template>
    <div>
        <form action="">
            <input type="text" name="" id="username" placeholder="Username">

            <button @click="submit">Submit</button>
        </form>
    </div>
</template>

<script>
    export default {
        methods: {
            submit() {
            this.$emit('submit', this.email)
            }
        }
    }
</script>
```

Our complete login component. Notice the additional change to the script section of the our component. Now all our tests should pass.

```bash
PASS  tests/unit/loginpage.spec.js
 login.vue
   ✓ should show the form element on the screen (11ms)
   ✓ should contain input fields (2ms)
   ✓ form should contain input fields with type="text" (1ms)
   ✓ button trigger event (5ms)

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        1.88s, estimated 2s
Ran all test suites.
```

To make our test codes easier, We can refractor by making the wrapper variable a global variable and our tests still passed.

Due to the isVisible being deprecated, we can use the `exists()` function. Testing depends on your contract with your end user.

You need to be sure "DO I CARE IF THIS CHANGE?" If you care, test, else move to the next detail. TDD helps write robust tests (not too many, not too few).

## Conclusion

1. An introduction to TEST DRIVEN DEVELOPMENT
2. Benefit of TDD.
3. Setting up our Vue project.
4. Writing our first tests suites successfully.

To get the full Vue project, clone it on [GitHub](https://github.com/D-Lite/vuetestapp)