Testing Vue components the right way
Tags: testing, vue, vitest, frontend
Published: 2023-02-06 12:23:46

---
title: "Testing Vue components the right way"
description: "Tips for planning and testing your Vue component with Vitest and Vue Test Utils"
cover_image: "https://res.cloudinary.com/mayashavin/image/upload/v1675682319/articles/testing/Testing.png"
tags: ['testing', 'vuejs', 'vitest', 'frontend']
published: true
canonical_url: "https://mayashavin.com/articles/testing-components-with-vitest"
---


This post will explore tips and practices we can use to simplify our Vue component testing with tools like Vitest.

## Table of Contents

- [Table of Contents](#table-of-contents)
- [Setting up Vitest](#setting-up-vitest)
- [Split your component into composable (unit block of code)](#split-your-component-into-composable-unit-block-of-code)
- [Mount mocked app for composable that uses life cycle hooks](#mount-mocked-app-for-composable-that-uses-life-cycle-hooks)
- [Flush promises for composable with asynchronous call](#flush-promises-for-composable-with-asynchronous-call)
- [Spy on and mock whatever you don't need to test (or have tested already)](#spy-on-and-mock-whatever-you-dont-need-to-test-or-have-tested-already)
- [Checking the coverage](#checking-the-coverage)
- [Summary](#summary)

Before we start, let's take a look of how to set up Vitest for our project quickly.

## Setting up Vitest

Vitest is the fast unit testing framework powered by Vite and for Vite-powered projects. It offers similar functionalities and syntax to Jest, with TypeScript/JSX supported out of the box. Many developers consider Vitest to be faster than Jest, especially in hot watch mode. 

To set up Vitest in Vue, you can choose it as part of the configuration for a new project generated by Vite, or manually add it to the dependencies using the following command:

```bash
npm install -D vitest

//OR
yarn add -D vitest
```

Then in `vite.config.js`, we add the following `test` object to the configuration, where we decide to use `jsdom` as our DOM environment:

```js
test: {
    environment: "jsdom",
}
```

We can also set `global: true` so that we don't have to import each method like the `expect`, `describe`, `it`, or `vi` instance explicitly from the `vitest` package in every test file. 

Once done with `vite.config.js`, we need to add a new `scripts` command to the `package.json` file of our project to run the tests:

```json
"scripts": {
    "test": "vitest --root src/",
}
```

In the above code, we set the root of our tests as the `src/` folder. We can also put it in `vite.config.js` under the `test.root` field. Both approaches work the same.

We are now ready to move on to testing our Vue component, starting with splitting our component's logic into reusable blocks of code.

## Split your component into composable (unit block of code)

Yes. Composition APIs make your life easier, and composable is your weapon to achieve reusability and separate the component logic from the component's UI. 

Take the following `Movies` for instance. This component displays a list of movies with the following features:

* The component fetches the movies list from an external source.
* Users can search for a movie by title.
* Optional - There should be a loading state when fetching the movies and an error state when something wrong happens during the fetch.

The below screenshot shows how the component should look on the UI:

<img src="https://res.cloudinary.com/mayashavin/image/upload/f_auto,q_auto/v1675680000/articles/testing/movies_ui" loading="lazy" alt="Screenshot showing how the list of movies look like on the browser" class="mx-auto"/>

And our component's template will contain the following code:

```html
<template>
    <div>
        <h1>Movies</h1>
        <div>
        <div>
            <label for="search">Search:</label>
            <input type="text" id="search" v-model="searchTerm" />
        </div>
        <ul>
                <li v-for="movie in filteredMovies" :key="movie.id">
                    <article>
                        <h2>{{ movie.Title }}</h2>
                        <img :src="movie.Poster" :alt="movie.Title" width="100" />
                        <p>Released on: {{ movie.Year }}</p>
                    </article>
                </li>
            </ul>
        </div>
    </div>
  </template>
```

You may think we can write the component's logic as follows:

```javascript
import { ref, onBeforeMount } from 'vue';

const movies = ref([]);

const searchTerm = ref("");
const filteredItems = computed(() => {
    return items.value.filter((item) => {
        return filters.some((filter) => {
        return item[filter]
            .toLowerCase()
            .includes(searchTerm.value.toLowerCase());
        });
    });
});

onBeforeMount(async () => {
    const response = await fetch("https://swapi.dev/api/films");

    if (!response.ok) {
    throw new Error("Failed to fetch movies");
    }

    const data = await response.json();

    movies.value = data.results;
});

```

However, testing or reading this component or adding new features can be painful as it grows larger. Also, some logic may be reusable for others, such as the fetch and search mechanism. Instead of writing all the code logic within a single component, we can split them to separate unit blocks of code, or composable, as follows:

1. `useMovies` - This composable will fetch the list of movies from external API and returns them ready to be used in the component. It can also return a loading state and error state when applicable.
2. `useSearch` - This composable will add the search feature to the component. It receives an original list of items and returns a reactive `searchTerm`, the filtered list, and a method to update the term accordingly.

Now our component logic will be much shorter:

```html
  <script setup>
  import { useMovies } from "../composables/useMovies.js";
  import { useSearch } from "../composables/useSearch.js";
  
  const { items: movies } = useMovies();
  const { searchTerm, filteredItems: filteredMovies } = useSearch(movies);
  </script>
```

And we can write tests dedicated to `useSearch`, `useMovies`, and integration tests for the component's rendering (when it is in loading, in error state, user change input, etc.). Doing so allows us to divide any potential bug (if any) into its related section and conquer (or fix) it without affecting other logic sections.

An example of the test cases the `useSearch` can be as below:

```javascript
import { expect, describe, it } from "vitest";
import { useSearch } from "./useSearch";

describe("useSearch", () => {
  it("should return default value of searchTerm and original items as filtered items", () => {
    //...
  });

  it("should update searchTerm and return filtered items by title accordingly", () => {
    //...
  });

  it("should return filtered items by description field and searchTerm accordingly", () => {
    //...
  })
});

```

Unit testing with Vitest (or Jest) for Vue composable can be straightforward without additional setup. However, in many cases where your composable contains life cycle hooks, we need a Vue testing component, which we explore next.

## Mount mocked app for composable that uses life cycle hooks

Vitest, or any testing library, doesn't bind or contain any Vue context. Standalone Composition APIs like `ref()`, `reactive()`, `computed()`, etc... can act as independent APIs for creating reactive variables without any Vue lifecycle involved. Vitest alone is good enough for testing for composable using these APIs only. 

However, once the composable triggers any Vue lifecycle hooks (`onBeforeMount`, `onMounted`, etc.), we need to wrap the composable inside a Vue component, simulating the desired lifecycle for testing it.

To create a Vue component using the `setup()` hook for testing, we make a utility function called `withSetup` as follows:

```js
export function withSetup(hook) {
    let result;

    const app = createApp({
        setup() {
            result = hook();
            return () => {};
        },
    });

    app.mount(document.createElement("div"));

    return [result, app];
}
```

Notice `withSetup` receives a composable (or hook), as return the result of the composable, and the app instance in the form of an array. You can place this function file in your application's `test-utils` folder or any place dedicated to testing utilities. 

Take our `useMovies` composable, with the following implementation that uses `onBeforeMount`, for instance:

```javascript
import { ref, onBeforeMount } from 'vue';

export const useMovies = () => {
  const items = ref([]);

  const fetchItems = async () => {
    try {
      const response = await fetch("https://swapi.dev/api/films");

      if (!response.ok) {
        throw new Error("Failed to fetch items");
      }

      const data = await response.json();

      items.value = data.results;

    } catch (err) {
      //do something
    } finally {
      //do something
    }
  };

  onBeforeMount(fetchItems);

  return {
    items,
  };
};

```

When testing `useMovies`, we can trigger `withSetup` with the composable as its parameter, as seen below:

```js
  it("should fetch movies", () => {
    const [results, app] = withSetup(useMovies);

    //Assert results
  });
```

And after we finish our test case, we need to unmount the `app`:

```javascript
  it("should fetch movies", () => {
    //...
    app.unmount()
  });
```

With `withSetup()`, we can ensure `onBeforeMount` will be triggered and can add tests for any related data changes within this hook. 

Nevertheless, for `useMovies`, we still have one problem. Vue triggers `onBeforeMount` (or any lifecycle hooks) in a synchronous order, and our `fetchItems` callback is asynchronous. Vue continues the creation process without waiting for the callback to resolve. And since Vue doesn't know when the asynchronous will resolve, using `$nextTick()` will not guarantee the new data changes applied to the composable's results. For such cases, we need the help of the `@vue/test-utils` package, as in the following section.

## Flush promises for composable with asynchronous call

`vue/test-utils` - or Vue Test Utils is the official testing library for the Vue component. It provides a set of utility functions for simplifying testing Vue components. You can use its functions to mount and simulate Vue component interactions. One of the functions we will use today for our `useMovies` test is `flushPromises`.

```javascript
import { flushPromises } from "@vue/test-utils";
```

`flushPromises` flushes all the resolved Promise handlers, ensuring all the asynchronous calls are complete before we start asserting. In our previous test, we need to change our test to be `async` and make sure we `await` for the `flushPromises` to finish before asserting the result, as in the following code:

```javascript
it("should fetch movies", async () => {
    const [results, app] = withSetup(useMovies);

    await flushPromises();

    //Assert results

    app.unmount()
});
```

Straightforward enough. We now have the composable's asynchronous test case with the lifecycle hook covered. 

In testing, we should keep things manageable for the stability and quality of our tests. For testing such composable like `useMovies` where we use the `fetch` function internally, we can't fully control the result of the composable for our testing purpose. Some examples are when the request fails or when the request is in progress, or when to resolve the request—calling the original `fetch` in our composable equals leaving the test result to the uncontrolled external API response, which can end up rejected when it should resolve (internet connection issue, for instance), long waiting time, etc. 

Instead, we should mock (or spy on).

## Spy on and mock whatever you don't need to test (or have tested already)

Mocking is a method we use in testing to simulate the behavior of external dependencies in a controlled way. Using mocks allows us to test the code without invoking the dependent process, such as using an actual `fetch` request in our `useMovies` mock. 

We use mocks based on the assumption that the mocked dependency works as it should. We test the unit code that uses that dependency and then assert how that unit code responds to different values of the mocked dependency. 

`fetch()` takes the URL path to the resource you request as mandatory and returns a `Promise` that resolves into a `Response` to that request. In our code example, `useMovies`, we will mock `global.fetch`, a built-in method of sending HTTP requests, with the `vi.spyOn()` where `vi` is the Vitest instance imported from `vitest` package.

```javascript
vi.spyOn(global, 'fetch')
```

`vi.spyOn()`, similar to `jest.spyOn()`, accepts two mandatory arguments: an object and a string indicating the target object's method we wish to spy on. It returns a new mock with a set of methods for mocking implementation or mocking return value. 

Here we will mock the return value of the `fetch()` for the `useMovies` test case. We can perform that action before all the tests start (`beforeAll`), before each test (`beforeEach`), or within the test case itself. In our below code example, we will mock the return value of `fetch()` using the `mockReturnValue` method before all the tests start:

```javascript
describe('useMovies', () => {
    beforeAll(() => {
        const mockFetch = vi.spyOn(global, 'fetch');

        mockFetch.mockReturnValue(createMockResolveValue({
            results: [
                {
                    title: "A New Hope",
                },
            ]
        }));
    })
})
```

Where `createdMockResolveValue` is a utility function that takes our desired results and wraps them with the correct response format (with a `json()` method and a status `ok`), as follows:

```javascript
function createMockResolveValue(data) {
    return { 
        json: () => new Promise((resolve) => resolve(data)), 
        ok: true 
    };
}
```

And with the above code, we now have our `fetch()` mocked with a designated returned value, allowing our code to run independently.

We can also perform the spying out of the scope of `beforeAll` but under the range of the test suite (within the `describe` callback), allowing us to reuse the mock instance in other test cases specifically if needed.

```javascript
describe('useMovies', () => {
    const mockFetch = vi.spyOn(global, 'fetch');
    
    it("should fetch movies", async () => {
        mockFetch.mockReturnValue(createMockResolveValue({
            results: [
                {
                    title: "A New Hope",
                },
            ]
        }));

        //...
    })
})
```

One important note is that once you mock something in your tests, you need to clear the mocks at the end of the test to avoid affecting other tests. For example, Vitest (or Jest) will use the returned value from a specific available `mockReturnValue` call within that test suite if there is no other implementation mock, leading to undesired behavior. To avoid such circumstances, we call `mockClear()` method at the end of every test (`afterEach`), as shown below:

```javascript
describe('useMovies', () => {
    const mockFetch = vi.spyOn(global, 'fetch');
    
    //tests

    afterEach(() => {
        mockFetch.mockClear();
    })
})
```

We clear the mocked values separately from the test cases as a best practice to keep our test code concise and relevant per test suite. Any standard behavior (such as imitating the same return value for every test or clearing the mocks) should be grouped into more generic functions for Vitest to run after/before continuing the test cases.

Using `vi.spyOn()` allows you to mock a method and track any of its calls. For example, if we want to make sure our `fetch()` in `useMovies` is triggered with the correct URL, and only once, we can perform the following:

```javascript
expect(mockFetch).toHaveBeenCalledTimes(1)
expect(mockFetch).toHaveBeenCalledWith("https://swapi.dev/api/films")
```

An alternative for spying on and mocking is using `global.fetch = vi.fn()`. However, I do not recommend using this approach, especially when you have a large codebase and can't control which component or composable may also use `fetch()`.

We have split our component code into composable and mocked whatever was needed. We can apply the same approaches to testing the component after testing its composable by imitating those composables' returned values to match our component's test cases. Also, whenever we need to add a new feature, such as a loading screen while loading the movies, we only need to add two tests:

* One to the component `Movies`'s UI with mocked returned value of `isLoading` from `useMovies` is `true`.
* The other test to `useMovies` where we assert the value of `isLoading` to be `true` before resolving the response. 

It seems straightforward enough. 

The next and final tip we will discuss is checking the test coverage, a significant part of ensuring our project's coverage range is at the desired level of testing.

## Checking the coverage

Writing tests is essential, and knowing whether you test all the required cases for your logic is even more critical. The most common testing coverage tool is [Istanbul](https://istanbul.js.org/), where you can see how well your tests exercise your code by lines, functions, and branches. Below is an example of how the test coverage report looks in your terminal:

<img src="https://res.cloudinary.com/mayashavin/image/upload/f_auto,q_auto/v1675680195/articles/testing/coverage_sample" loading="lazy" alt="Screenshot showing the coverage status of each code file as table" class="mx-auto"/>

You can also define the coverage report in HTML format or export it to a file by configuring your coverage tool with various available reporter tools. 

For Vitest, to use the Istanbul coverage tool, you need to install an additional package `@vitest/coverage-istanbul` with the following command:

```bash
yarn add -D @vitest/coverage-istanbul

//OR
npm i -D @vitest/coverage-istanbul
```

Then, in your `vitest.config.js`, add a new field `coverage` within the `test` object, as follows:

```javascript
test: {
    globals: true,
    environment: "jsdom",
    coverage: {
      provider: 'istanbul'
    }
}
```

You can also set up the additional reporter tool, such as in HTML, as below:

```javascript
test: {
    globals: true,
    environment: "jsdom",
    coverage: {
        provider: 'istanbul',
        reporter: ['html']
    }
}
```

Then we add a new `scripts` command for the coverage report in your project's `package.json`:

```json
"scripts": {
    "coverage": "vitest run --coverage"
},
```

Upon running the command `yarn coverage` (or `npm coverage`), besides the terminal output report, there will be a folder `coverage` created in your project's directory, with dedicated `html` report files for folders, as seen below:

<img src="https://res.cloudinary.com/mayashavin/image/upload/f_auto,q_auto/v1675680195/articles/testing/coverage_folder" loading="lazy" alt="An example output of the coverage folder" class="mx-auto"/>

When you open `coverage/composables/index.html` on the browser, it will show you the report status of each composable testing coverage within the folder in a much more readable HTML format:

<img src="https://res.cloudinary.com/mayashavin/image/upload/f_auto,q_auto/v1675680195/articles/testing/html_coverage" loading="lazy" alt="Screenshot showing the coverage status of each code file as table in HTML format" class="mx-auto"/>

Finally, we can configure our project's testing coverage thresholds (in a more straightforward explanation - the minimum percentage of code coverage we want our tests to achieve) by assigning values to the fields `branches`, `functions`, or `lines`, or `statements` within the `coverage` object in the `vite.test.js` file, as follows:

```javascript
coverage: {
    branches: 80,
    functions: 80,
    lines: 80,
    statements: 10,
}
```

Each number assigned to the above fields represents the percentage we wish to achieve. With this configuration, we finally have our testing quality standards defined. If there is any test that doesn't meet the required expectations, we will see it both in the HTML report and in the terminal, like in the screenshot below:

<img src="https://res.cloudinary.com/mayashavin/image/upload/f_auto,q_auto/v1675680195/articles/testing/coverage_threholds" loading="lazy" alt="Screenshot showing the coverage status of each code file as table and a coverage percentage status at the bottom" class="mx-auto"/>

Great, right? And that's it. You can now go ahead and test your components efficiently.

Full demo code is available [here](https://github.com/mayashavin/component-testing-vitest)

## Summary

Testing is mandatory for code confidence, though it can sometimes be challenging. However, with the proper planning for your code and component, you can quickly achieve the required test coverage for your code and build a readable code system for your project. Splitting your component's logic into composable when applicable and dividing your logic into small and testable functions are some practices you can always apply to protect your code from the bottom. 

And finally, remember to mock and mock **responsibly** for your tests. 

What's next? It's time to go and write some tests 😉, maybe even testing your component's UI-only features like accessibility support with `axe-core`?

👉 _If you'd like to catch up with me sometimes, follow me on [Twitter](https://twitter.com/MayaShavin) | [Facebook](https://www.facebook.com/mayashavin/)._

Like this post or find it helpful? Share it 👇🏼 😉