How to write tests for Vue

Let us continue the trend of making sure our applications still work and continue working for the rest of 2021. Last week we talked about how to write tests for your JS app. Let's extend that to modern framework.

Most of people are probably writing their apps in a modern library or framework like React, Vue, or Angular. These frameworks help you create your UI and functionalities faster and more maintainable in components. Think of them as units or blocks that make up your whole UI. Depending on how you organize your components, you can have buttons, inputs, and some other simple ones that standardize on the same styles or more complicated ones like date picker, search result list and its items that you can use on different places with the same functionalities.

What differs between these UI components and JavaScript functions is just that UI component has, well, UI portion. At the end of the day it will be rendered and show HTML on the page for users to interact with. This opens up different ways of testing and not only limited to just testing if the JavaScript function works.

I mainly use Vue these days so this article will focus on Vue. However the method and concept should be similar in other frameworks with slight differences in what tools to use and maybe best practices. The sample codes and file setup are inheriting from how to write tests for your JS app.

Preparation

No matter which testing library to use, we will need to have the following modules installed.

bash
# npm
npm install vue vue-template-compiler

# yarn
yarn add vue vue-template-compiler

We also need some other libraries so Vue files and the corresponding JS files can be transpiled correctly during test.

bash
# npm
npm install babel-jest vue-jest@4.0.0-beta.2 jest-transform-stub @babel/core

# yarn
yarn add babel-jest vue-jest@4.0.0-beta.2 jest-transform-stub @babel/core

Setup

It is relatively easy to set up before writing your tests. We need to update jest.config.js so jest can know what to do with Vue files, particularly moduleFileExtensions and tranform props. I've also added setupFiles prop to include a file that jest will always run first when running tests.

jsx
// jest.config.js
module.exports = {
  // Automatically clear mock calls and instances between every test
  clearMocks: true,

	// An array of glob patterns indicating a set of files for which coverage information should be collected
  collectCoverageFrom: [
    '<rootDir>/src/**/*.{js,vue}',
  ],

  // The glob patterns Jest uses to detect test files
  testMatch: ["**/test/**/*.spec.js"],

  // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
  testPathIgnorePatterns: ["/node_modules/"],
	
	// The paths to modules that run some code to configure or set up the testing environment before each test
  setupFiles: ['<rootDir>/jest.init.js'],

  **// An array of file extensions your modules use
  moduleFileExtensions: ["js", "vue"],

  // A map from regular expressions to paths to transformers
  transform: {
    '.+\\.js$': '<rootDir>/node_modules/babel-jest',
    '.*\\.(vue)$': '<rootDir>/node_modules/vue-jest',
    '.*\\.(css|scss)$': '<rootDir>/node_modules/jest-transform-stub',
  },**
};
jsx
// jest.init.js
require('regenerator-runtime/runtime');

jest.init.js is useful if you need to define your own mock or global import something before tests run. In this case we include the polyfill that allows async await.

Vue Test Utils

Vue test utils is one of the most common libraries to use for testing Vue application. It provides some methods like mount to mount your components and interact with them.

Install

bash
# npm
npm install @vue/test-utils vue-jest -D

# yarn
yarn add @vue/test-utils vue-jest -D

Sample

Imagine we have a Vue component like:

jsx
<template>
  <div class="item">
    <span>Count: {{ count }}</span>

    <button @click="incrementCount">Increment Count</button>
  </div>
</template>

<script>
export default {
  name: "Counter",

  data() {
    return {
      count: 0,
    };
  },

  methods: {
    incrementCount() {
      this.count += 1;
    },
  },
};
</script>

It's a simple counter that increments the count when the button is clicked. Let's test if the count on UI increments when the button is clicked.

jsx
import { mount } from "@vue/test-utils";
import Counter from "../src/Counter.vue";

describe("Counter Component Test Utils", () => {
  it("should increment counter when click the button", async () => {
    const wrapper = mount(Counter);
    const button = wrapper.find("button");
    await button.trigger("click");
    expect(wrapper.find("span").text()).toContain("1");
  });
});

You can know more about vue test utils from their documentation.

Testing Library

Testing library is another library dedicated to testing UI components. The concept and initiative is a bit different than what vue test utils allows you to do.

The more your tests resemble the way your software is used, the more confidence they can give you.

We write these unit tests to make sure our code works. In JavaScript, "code works" means the function returns the correct output given the input. In component, it usually means if the UI renders correctly with the correct states. If we can test and ensure UI renders fine, we have confidence that our code works. With that, Testing Library is more focusing on how we can write tests for the UI rather than the implementation detail. This way our tests are more maintainable as they don't get into too much detail but still test enough to give us confidence.

This doesn't mean you can only use Testing Library to achieve this principle. We see that it is possible with vue test utils in the test example. Testing Library just provides more methods that make it easier so at the end of the day it is a matter of preference. You can know more about it from their documentation.

Install

bash
# npm
npm install @testing-library/jest-dom @testing-library/vue -D

# yarn
yarn add @testing-library/jest-dom @testing-library/vue -D

Sample

Using the same count sample, our test becomes:

jsx
import { render, fireEvent } from "@testing-library/vue";
import Counter from "../src/Counter.vue";

describe("Counter Component Testing Library", () => {
  it("should increment counter when click the button", async () => {
    const { getByText } = render(Counter);
    const button = getByText("Increment Count");
    await fireEvent.click(button);
    getByText("Count: 1");
  });
});

If we run the test with coverage on, we would get:

Test coverage result

Conclusion

I personally like the way Testing Library proposes but I did need some time to work my mind around the idea. During that stage I found it was inconvenient to not have a Vue instance I could play with but now I feel it's much easier once I realize I am only testing the UI. Both of these libraries support vue router and vuex during the test. So at the end, it comes down to your preference.

Are you writing tests or have tests helped you prevent disaster? Tweet to me to let me know!