A gentle introduction to testing vue applications.
Tags: vue, javascript, jest
Published: 2018-04-13 15:33:22

---
title: A gentle introduction to testing vue applications.
published: true
description: learn how to test vue-js applications and components
tags: vuejs, javascript, jest
cover_image: https://thepracticaldev.s3.amazonaws.com/i/zsvvrssf47ydw1k4rz9n.jpeg
---

### Introduction

In this tutorial, we'll cover an introduction to testing vue-js applications and components. We'll be testing [this simple todo application](https://vue-todos.netlify.com/). 

The source code for this application lives [here](https://github.com/bahdcoder/testing-vue-apps).

To keep things simple, this application is built with one component, `App.vue`. Here's how it looks:

```js

// src/App.vue

<template>
<div class="container text-center">
  <div class="row">
    <div class="col-md-8 col-lg-8 offset-lg-2 offset-md-2">
      <div class="card mt-5">
      <div class="card-body">
        <input data-testid="todo-input" @keyup.enter="e => editing ? updateTodo() : saveTodo()" v-model="newTodo" type="text" class="form-control p-3" placeholder="Add new todo ...">
        <ul class="list-group" v-if="!editing" data-testid="todos">
          <li :data-testid="`todo-${todo.id}`" class="list-group-item" v-for="todo in todos" :key="todo.id">
            {{ todo.name }}
            <div class="float-right">
              <button :data-testid="`edit-button-${todo.id}`" class="btn btn-sm btn-primary mr-2" @click="editTodo(todo)">Edit</button>
              <button :data-testid="`delete-button-${todo.id}`" class="btn btn-sm btn-danger" @click="deleteTodo(todo)">Delete</button>
            </div>
          </li>
        </ul>
      </div>
    </div>
    </div>
  </div>
</div>
</template>

<script>
import axios from 'axios'

export default {
  name: 'app',
  mounted () {
    this.fetchTodos()
  },
  data () {
    return {
      todos: [],
      newTodo: '',
      editing: false,
      editingIndex: null,
      apiUrl: 'https://5aa775d97f6fcb0014ee249e.mockapi.io'
    }
  },
  methods: {
    async saveTodo () {
      const { data } = await axios.post(`${this.apiUrl}/todos`, {
        name: this.newTodo
      })

      this.todos.push(data)

      this.newTodo = ''
    },
    async deleteTodo (todo) {
      await axios.delete(`${this.apiUrl}/todos/${todo.id}`)
      this.todos.splice(this.todos.indexOf(todo), 1)
    },
    editTodo (todo) {
      this.editing = true
      this.newTodo = todo.name

      this.editingIndex = this.todos.indexOf(todo)
    },
    async updateTodo () {
      const todo = this.todos[this.editingIndex]

      const { data } = await axios.put(`${this.apiUrl}/todos/${todo.id}`, {
        name: this.newTodo
      })

      this.newTodo = ''
      this.editing = false

      this.todos.splice(this.todos.indexOf(todo), 1, data)
    },
    async fetchTodos () {
      const { data } = await axios.get(`${this.apiUrl}/todos`)

      this.todos = data
    }
  }
}
</script>
```
### Brief application overview.

The application we are testing is a CRUD to-dos application. 
- When the component is mounted, a `fetchTodos` function is called. This function calls an external API and gets a list of todos.
- The list of to-dos is displayed in an unordered list.
- Each list item has a dynamic `data-testid` attribute generated using the unique id of the to-do. This would be used for our tests later. If you want to understand why we would use data attributes over traditional classes and ids, have a look at [this](https://blog.kentcdodds.com/making-your-ui-tests-resilient-to-change-d37a6ee37269).
- The unordered list, input field, edit and delete buttons also have `data-testid` attributes.

### Setup

- Clone the GitHub repository locally and install all npm dependencies:

```bash

git clone https://github.com/bahdcoder/testing-vue-apps

cd testing-vue-apps && npm install

```

- Install the packages we need for testing: 
    - `@vue/test-utils` package, which is the official testing library for vuejs.
    - `flush-promises` package, which is a simple package that flushes all pending resolved promise handlers (we'll talk more about this later).

```bash

npm i --save-dev @vue/test-utils flush-promises

```

- We'll create a mock for the `axios` library, which we'll use for our tests since we do not want to make real API requests during our tests. Create a `test/__mocks__/axios.js` file, and in it, paste the following mock:


```js
// __mocks__/axios.js


export default {
  async get () {
    return {
      data: [{
        id: 1,
        name: 'first todo'
      }, {
        id: 2,
        name: 'second todo'
      }]
    }
  },
  async post (path, data) {
    return {
      data: {
        id: 3,
        name: data.name
      }
    }
  },
  async delete (path) {},
  async put (path, data) {
    return {
      data: {
        id: path[path.length - 1],
        name: data.name
      }
    }
  }
}


```

Jest will automatically pick up this file, and replace it with the installed `axios` library when we are running our tests. For example, the `get` function returns a promise that resolves with two todos, and every time `axios.get` is called in our application, jest will replace this functionality with the one in our mock.


### Writing our first test

In the `tests/unit` directory, create a new file called `app.spec.js`, and add this to it:


```js

// tests/unit/app.spec.js

import App from '@/App.vue'
import { mount } from '@vue/test-utils'

describe('App.vue', () => {
  it('displays a list of todos when component is mounted', () => {
    const wrapper = mount(App)
  })
})


```

The first thing we did was import the `App.vue` component, and `mount` function from the `@vue/test-utils` library. 

Next, we call the `mount` function passing in the `App` component as a parameter.

The mount function renders the App component just like the component would be rendered in a real browser, and returns a wrapper. This wrapper contains a whole lot of helper functions for our tests as we'll see below.

As you can see, we want to test that a list of todos is fetched from the API, and displayed as an unordered list when the component is mounted.

Since we have already rendered the component by calling the `mount` function on it, we'll search for the list items, and make sure they are displayed.

```js
// app.spec.js
  it('displays a list of todos when component is mounted', () => {
    const wrapper = mount(App)

    const todosList = wrapper.find('[data-testid="todos"]')
    expect(todosList.element.children.length).toBe(2)
  })

```

- The `find` function on the wrapper takes in a `CSS selector` and finds an element in the component using that selector.

Unfortunately, running this test at this point fails because the assertions run before the `fetchTodos` function resolves with the todos. To make sure our axios mock resolves with the list of to-dos before our assertion runs, we'll use our `flush-promises` library as such:


```js

// app.spec.js

import App from '@/App.vue'
import { mount } from '@vue/test-utils'
import flushPromises from 'flush-promises'

describe('App.vue', () => {
  it('displays a list of todos when component is mounted', async () => {
    // Mount the component
    const wrapper = mount(App)
    // Wait for fetchTodos function to resolve with list of todos
    await flushPromises()

    // Find the unordered list
    const todosList = wrapper.find('[data-testid="todos"]')

    // Expect that the unordered list should have two children
    expect(todosList.element.children.length).toBe(2)
  })
})



```

The `find` function returns a wrapper, and in there we can get the real `DOM-element`, which is saved on the `element` property. We, therefore, assert that the number of children should equal two (since our `axios.get` mock returns an array of two to-dos).

Running our test now passes. Great!



### Testing a user can delete a todo

Each to-do item has a delete button, and when the user clicks on this button, it should delete the todo, and remove it from the list.

```js

// app.spec.js


  it('deletes a todo and removes it from the list', async () => {
    // Mount the component
    const wrapper = mount(App)

    // wait for the fetchTodos function to resolve with the list of todos.
    await flushPromises()

    // Find the unordered list and expect that there are two children
    expect(wrapper.find('[data-testid="todos"]').element.children.length).toBe(2)

    // Find the delete button for the first to-do item and trigger a click event on it.
    wrapper.find('[data-testid="delete-button-1"]').trigger('click')

    // Wait for the deleteTodo function to resolve.
    await flushPromises()

    // Find the unordered list and expect that there is only one child
    expect(wrapper.find('[data-testid="todos"]').element.children.length).toBe(1)


    // expect that the deleted todo does not exist anymore on the list
    expect(wrapper.contains(`[data-testid="todo-1"]`)).toBe(false)
  })

```

We introduced something new, the `trigger` function. When we find an element using the `find` function, we can trigger DOM events on this element using this function, for example, we simulate a click on the delete button by calling `trigger('click')` on the found todo element.

When this button is clicked, we call the `await flushPromises()` function, so that the `deleteTodo` function resolves, and after that, we can run our assertions.

We also introduced a new function, `contains`, which takes in a `CSS selector`, and returns a boolean, depending on if that element exists in the `DOM` or not.

Therefore for our assertions, we assert that the number of list items in the `todos` unordered list is one, and finally also assert that the DOM does not contain the list item for the to-do we just deleted.


### Testing a user can create a todo

When a user types in a new to-do and hits the enter button, a new to-do is saved to the API and added to the unordered list of to-do items.

```js
// app.spec.js

  it('creates a new todo item', async () => {
    const NEW_TODO_TEXT = 'BUY A PAIR OF SHOES FROM THE SHOP'

    // mount the App component
    const wrapper = mount(App)

    // wait for fetchTodos function to resolve
    await flushPromises()

    // find the input element for creating new todos
    const todoInput = wrapper.find('[data-testid="todo-input"]')

    // get the element, and set its value to be the new todo text
    todoInput.element.value = NEW_TODO_TEXT

    // trigger an input event, which will simulate a user typing into the input field.
    todoInput.trigger('input')

    // hit the enter button to trigger saving a todo
    todoInput.trigger('keyup.enter')

    // wait for the saveTodo function to resolve
    await flushPromises()

    // expect the the number of elements in the todos unordered list has increased to 3
    expect(wrapper.find('[data-testid="todos"]').element.children.length).toBe(3)

    // since our axios.post mock returns a todo with id of 3, we expect to find this element in the DOM, and its text to match the text we put into the input field.
    expect(wrapper.find('[data-testid="todo-3"]').text())
      .toMatch(NEW_TODO_TEXT)
  })


```
Here's what we did:


- We found the input field using its `data-testid attribute selector`, then set its value to be the `NEW_TODO_TEXT` string constant. Using our trigger function, we triggered the `input` event, which is equivalent to a user typing in the input field.

- To save the todo, we hit the enter key, by triggering the `keyup.enter` event. Next, we call the `flushPromises` function to wait for the `saveTodo` function to resolve. 

- At this point, we run our assertions:
    - First, we find the unordered list and expect that it now has three to-dos: two from calling the `fetchTodos` function when the component is mounted, and one from just creating a new one.
    - Next, using the `data-testid`, we find the specific to-do that was just created ( we use `todo-3` because our mock of the `axios.post` function returns a new todo item with the `id` of 3). 
    - We assert that the text in this list item equals the text we typed in the input box at the beginning of the text.
    - Note that we use the `.toMatch()` function because this text also contains the `Edit` and `Delete` texts. 


### Testing a user can update a todo

Testing for the update process is similar to what we've already done. Here it is:


```js

// app.spec.js


  it('updates a todo item', async () => {
    const UPDATED_TODO_TEXT = 'UPDATED TODO TEXT'

    // Mount the component
    const wrapper = mount(App)

    // Wait for the fetchTodos function to resolve
    await flushPromises()

    // Simulate a click on the edit button of the first to-do item
    wrapper.find('[data-testid="edit-button-1"]').trigger('click')

    // make sure the list of todos is hidden after clicking the edit button
    expect(wrapper.contains('[data-testid="todos"]')).toBe(false)

    // find the todo input
    const todoInput = wrapper.find('[data-testid="todo-input"]')

    // set its value to be the updated texr
    todoInput.element.value = UPDATED_TODO_TEXT

    // trigger the input event, similar to typing into the input field
    todoInput.trigger('input')

    // Trigger the keyup event on the enter button, which will call the updateTodo function
    todoInput.trigger('keyup.enter')

    // Wait for the updateTodo function to resolve.
    await flushPromises()

    // Expect that the list of todos is displayed again
    expect(wrapper.contains('[data-testid="todos"]')).toBe(true)

    // Find the todo with the id of 1 and expect that its text matches the new text we typed in.
    expect(wrapper.find('[data-testid="todo-1"]').text()).toMatch(UPDATED_TODO_TEXT)
  })

```

Running our tests now should be successful. Awesome!