TutorialsLast Updated Apr 24, 20257 min read

Automate testing for a Vue.js application

Olususi Oluyemi

Fullstack Developer and Tech Author

Developer D sits at a desk working on a beginning-level project.

One of the leading frameworks in the JavaScript community, Vue.js is a progressive framework for building reusable components for the web user interface. Its intuitive API and robust flexibility for handling front-end logic are just two of the reasons that Vue has been adopted by developers worldwide.

In this tutorial, I will lead you through building a simple listing application that shows the names and roles of users. I will show you how to write tests for the application. Finally, you will configure a continuous integration (CI) pipeline for automating testing.

Prerequisites

For this tutorial, you will need:

  • Node.js installed on your system, preferably version >=20.11.0.
  • A CircleCI account.
  • A GitHub account.
  • Familiarity with building apps with Vue.js.

Getting started

As at the time of writing this tutorial, create-vue is the recommended way to scaffold new Vue.js projects. This tool helps you create a new Vue.js project with a single command. It also provides a list of features to include in the project, such as Vitest for unit testing.

Get started by creating a new Vue.js project with create-vue using this command:

npm create vue@latest

You will be prompted to answer a few questions. Here are the answers you should provide:

  • Project name: vuejs-user-app
  • Select features to include in your project: Vitest (unit testing)
┌ Vue.js - The Progressive JavaScript Framework
│
◇ Project name (target directory):
│ vuejs-user-app
│
◇ Select features to include in your project: (↑/↓ to navigate,
space to select, a to toggle all, enter to confirm)
│ Vitest (unit testing)

Scaffolding project in /Users/yemiwebby/tutorial/circleci/vuejs-user-app...
│
└ Done. Now run:

cd vuejs-user-app
npm install
npm run dev

Next, change into the project, install its dependencies and run it with:

cd vuejs-user-app

npm install

npm run dev

You can review the application in your browser at http://localhost:5173.

Vue homepage

This renders the default homepage for a new Vue.js application. You will change this by creating new reusable components in the next section of this tutorial.

Stop the application from running using CTRL + C.

Creating reusable Vue components

Vue.js components contain three different sections for building web applications. They are:

  • <template></template>
  • <script></script>
  • <style></style>

These sections help create a proper structure for the view, business logic, and styling.

Creating the user component

The first component that you will add to the application is for creating and listing users. This component houses a form with an input field to accept the name of a particular user. When the form is submitted, the details from the input field are pushed to a dummy users array created for testing purposes.

To begin, create a new file named UserList.vue within the ./src/components folder. Open this new file and paste this content:

<template>
  <div class="container">
    <div class="page-title">
      <h3>{{ message }}</h3>
    </div>
    <div class="row">
      <div class="col-md-4" v-for="user in users" :key="user.id" data-user="user">
        <div class="m-portlet m-portlet--full-height">
          <div class="m-portlet__body">
            <div class="tab-content">
              <div class="tab-pane active" id="m_widget4_tab1_content">
                <div class="m-widget4 m-widget4--progress">
                  <div class="m-widget4__item">
                    <div class="m-widget4__img m-widget4__img--pic">
                      <img src="https://bootdey.com/img/Content/avatar/avatar1.png" alt="" />
                    </div>
                    <div class="m-widget4__info">
                      <span class="m-widget4__title"> {{ user.name }} </span>
                      <br />
                      <span class="m-widget4__sub">
                        {{ user.title }}
                      </span>
                    </div>
                    <div class="m-widget4__ext">
                      <button
                        @click="deleteUser(user)"
                        class="btn btn-primary"
                        data-cy="taskDelete"
                        id="deleteForm"
                      >
                        Delete
                      </button>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div class="row">
      <form id="form" @submit.prevent="createUser">
        <input id="new-user" v-model="newUser" class="form-control" />
      </form>
    </div>
  </div>
</template>

This is the <template></template> section that renders the users list to the view. It also contains an input field to post the name of a new user.

Next, paste this code just after the end of the </template> tag:

<script>
export default {
  props: ["message"],
  data() {
    return {
      newUser: "",
      users: [
        {
          id: 1,
          name: "Anna Strong",
          title: "Software Engineer",
        },
        {
          id: 2,
          name: "John Doe",
          title: "Technical Writer",
        },
      ],
    };
  },
  methods: {
    createUser() {
      this.users.push({
        id: 3,
        name: this.newUser,
        title: "Crypto Expert",
      });
      this.newUser = "";
    },

    deleteUser(user) {
      const newList = this.users.filter((u) => user.id !== u.id);
      this.users = newList;
    },
  },
};
</script>

This defines a users array with dummy data to be rendered on the page. The createUser() method receives the details of a new user via the input field and pushes it to the users array. You also defined a method named deleteUser() which accepts a user object as a parameter and removes it from the user list when invoked.

Creating the header component

To create a component for the Header section of the view, go to ./src/components folder and create a new file named NavBar.vue. Paste this code into it:

<template>
  <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <div class="collapse navbar-collapse justify-content-md-center" id="navbarsExample08">
      <ul class="navbar-nav">
        <li class="nav-item active">
          <a class="nav-link" href="#"> Users Listing App <span class="sr-only">(current)</span></a>
        </li>
      </ul>
    </div>
  </nav>
</template>

Updating the app component

Open the application’s App.vue component and update it by including the links to both the NavBar and UserList components. Replace its content with this:

<template>
  <div>
    <NavBar />
    <UserList />
    <div class="container"></div>
  </div>
</template>

<script>
import NavBar from "./components/NavBar.vue";
import UserList from "./components/UserList.vue";

export default {
  name: "App",
  components: { NavBar, UserList },
};
</script>

<style>
body {
  background: #eee;
}
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
.page-title {
  margin: 15px 15px;
}
.m-portlet {
  margin-bottom: 2.2rem;
}
.m-portlet {
  -webkit-box-shadow: 0 1px 15px 1px rgba(113, 106, 202, 0.08);
  -moz-box-shadow: 0 1px 15px 1px rgba(113, 106, 202, 0.08);
  box-shadow: 0 1px 15px 1px rgba(113, 106, 202, 0.08);
  background-color: #fff;
}
.m-portlet .m-portlet__head {
  border-bottom: 1px solid #ebedf2;
}
.m-widget4 .m-widget4__item {
  display: table;
  padding-top: 1.15rem;
  padding-bottom: 1.25rem;
}
.m-widget4 .m-widget4__item .m-widget4__img {
  display: table-cell;
  vertical-align: middle;
}
.m-widget4 .m-widget4__item .m-widget4__img.m-widget4__img--logo img {
  width: 3.5rem;
  border-radius: 50%;
}
.m-widget4 .m-widget4__item .m-widget4__img.m-widget4__img--pic img {
  width: 4rem;
  border-radius: 50%;
}
.m-widget4 .m-widget4__item .m-widget4__img.m-widget4__img--icon img {
  width: 2.1rem;
}
.m-widget4 .m-widget4__item .m-widget4__info {
  display: table-cell;
  width: 100%;
  padding-left: 1.2rem;
  padding-right: 1.2rem;
  font-size: 1rem;
  vertical-align: middle;
}
.m-widget4 .m-widget4__item .m-widget4__info .m-widget4__title {
  font-size: 1rem;
  font-weight: bold;
}
.m-widget4.m-widget4--progress .m-widget4__info {
  width: 50%;
}
</style>

This includes a <style></style> section to include styling for the app.

Including Bootstrap

Open the index.html file within the public folder and include the CDN file for Bootstrap. This is just to give the page some default style.

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <link rel="icon" href="/favicon.ico" />

    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
      crossorigin="anonymous"
      referrerpolicy="no-referrer"
    />

    <title>Circle CI User App</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

Lastly, update the ssrc/main.js file to remove the default styles:

// import './assets/main.css' // remove this

import { createApp } from "vue";
import App from "./App.vue";

createApp(App).mount("#app");

Now, run the application again with npm run dev. Visit http://localhost:5173. User listing app

Now that your application is up and running you can start unit testing for the UserList component.

Unit testing Vue.js components

There are many testing frameworks for JavaScript applications. Arguably, vitest and Jest stand out as the most popular ones. For this tutorial, you will be using vitest to test your application.

Vue Test Utils(VTU) is a testing library built on Jest. It was designed to simplify testing Vue.js components by providing utility functions to mount and interact with isolated Vue components.

First, create tests/unit directory that will house all the unit tests. Jest will search for your unit tests files here once the command for testing is issued.

If you followed the steps above to create the Vue.js project with create-vue, you should have vitest installed. If you didn’t use create-vue to scaffold the project and you want to use vitest to test your Vue.js application with Vite, you need to install the required packages using this command:

npm install -D vitest @vue/test-utils @vitejs/plugin-vue

Vitest is a test runner created by Vue and test-utils is the testing library that enables you to interact with your Vue application. The plugin-vue package helps vitejs test Vue applications.

Now, create a file in the application root directory named vite.config.js and add this content:

import { fileURLToPath } from "node:url";
import { mergeConfig, defineConfig, configDefaults } from "vitest/config";
import viteConfig from "./vite.config";

export default mergeConfig(
  viteConfig,
  defineConfig({
    test: {
      environment: "jsdom",
      exclude: [...configDefaults.exclude, "e2e/**"],
      root: fileURLToPath(new URL("./", import.meta.url)),
    },
  })
);

This configuration file tells the vitejs runner that the target application is a Vue application and to use the mounting capabilities of the test runner.

Writing tests for the application

In this section, we will write the unit test for the UserList component. In that component we want to:

  • Mount the component and check that it can render props passed to it.
  • Find elements within the component and render the user list.
  • Submit a form, then create a new user and add it to the list of existing users.

To begin, go to the tests/unit folder and create user.spec.js. Open the file and replace its content with this:

import { mount } from "@vue/test-utils";
import UserList from "../../components/UserList.vue";
import { describe, it, expect, test } from "vitest";

describe("User List component unit tests: ", () => {
  it("renders props when passed", () => {
    const message = "new message";
    const wrapper = mount(UserList, {
      props: { message },
    });
    expect(wrapper.text()).toMatch(message);
  });

  test("Renders the list", () => {
    const wrapper = mount(UserList);
    const name = "Anna Strong";
    const user = wrapper.get('[data-user="user"]');
    expect(user.text()).toContain(name);
    expect(wrapper.findAll('[data-user="user"]')).toHaveLength(2);
  });

  test("creates a user", async () => {
    const wrapper = mount(UserList);
    const newName = "John Doe";
    await wrapper.get('[id="new-user"]').setValue(newName);
    await wrapper.get('[id="form"]').trigger("submit");
    expect(wrapper.findAll('[data-user="user"]')).toHaveLength(3);
  });
});

In this file, you imported a function named mount from the vue-test-utils library to help mount an instance of the UserList component.

You started by writing a test to assert that the component can render a prop passed into it from a parent component. Next, you targeted the data attribute within the view of the UserList component and ensured that it contains a specific name of one of the users and renders the default length from the users array.

Lastly, you created a test function to ensure that a new user can be created within the component.

Running the test locally

To confirm that the defined tests are passing, enter this command from the terminal:

npm run test:unit

This is the terminal output:

> vuejs-user-app@0.0.0 test:unit
> vitest

 DEV  v3.0.9 /Users/yemiwebby/tutorial/circleci/vuejs-user-app

 ✓ src/components/__tests__/HelloWorld.spec.js (1 test) 10ms
 ✓ src/tests/unit/user.spec.js (3 tests) 28ms

 Test Files  2 passed (2)
      Tests  4 passed (4)
   Start at  15:42:37
   Duration  655ms (transform 102ms, setup 0ms, collect 199ms, tests 39ms, environment 742ms, prepare 78ms)

In the next section, you will automate this test using CircleCI.

Automating Vue tests with CI

In this section you will create a configuration file for your project’s CI pipeline. With CI, you can automate the process of building, testing, and deploying your code with every commit or pull request. This ensures that your software is always in a deployable state, helps you catch and fix issues early, and improves the quality and reliability of your application.

Create a .circleci folder at the root of the project and create a new file named config.yml within it. Add this content:

version: 2.1
jobs:
  build-and-test:
    working_directory: ~/project
    docker:
      - image: cimg/node:23.10.0
    steps:
      - checkout
      - run:
          name: Update NPM
          command: "sudo npm install -g npm"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: Install Dependencies
          command: npm install --legacy-peer-deps
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: Run test for the application
          command: npm run test:unit
workflows:
  build-and-test:
    jobs:
      - build-and-test

This file defines a job to build and run the test command for your project. It pulls in the cimg/node:23.10.0 Docker image from the CircleCI Docker image registry and uses it to install all the project’s dependencies before running the test.

Now that the configuration file has been set up, you need to set up a repository on GitHub and link the project to CircleCI. Review Pushing your project to GitHub for step-by-step instructions.

Setting up the project on CircleCI

Log in to your CircleCI account with the linked GitHub account to view all your repositories on the projects’ dashboard. Search for the vuejs-user-app project and click Set Up Project to continue.

Select project

You will be prompted to choose a configuration file. Select the .circleci/config.yml file in your repo and enter the name of the branch where your code is stored. Click Set Up Project to start the workflow.

Select config file

This will initialize the workflow and run the test for your project.

Successful workflow

Conclusion

In this tutorial, we built a listing application with Vue.js and covered the required steps to write a unit test for its component. We then used CircleCI infrastructure to configure a continuous integration pipeline to automate the testing process.

I hope that you found this tutorial helpful. Check here on GitHub for the complete source code for the project built in this guide.


Oluyemi is a tech enthusiast with a background in Telecommunication Engineering. With a keen interest in solving day-to-day problems encountered by users, he ventured into programming and has since directed his problem solving skills at building software for both web and mobile. A full stack software engineer with a passion for sharing knowledge, Oluyemi has published a good number of technical articles and blog posts on several blogs around the world. Being tech savvy, his hobbies include trying out new programming languages and frameworks.

Copy to clipboard