DEV Community

Cover image for Component Testing in Vue: Using routing for states
Maya Shavin πŸŒ·β˜•οΈπŸ‘
Maya Shavin πŸŒ·β˜•οΈπŸ‘

Posted on • Originally published at mayashavin.com

Component Testing in Vue: Using routing for states

In the previous post we explored how to set up Playwright with component testing in a Vue application, as well as wrote our first few component tests. In this post, we will expand further in testing the components that use Vue Router to synchronize their states with the URL params

Table of content

Pre-requisites

This post assumes you have a Vue project set up wit Vue Router and Playwright component testing. If you haven't done so, please refer to the previous post to set up Playwright with component testing for your Vue project.

Create and map ItemsView with the right route

We will create a new component ItemsView that contains a search input and a list of items. The search input will filter the items based on the search term, with the following code:

<template>
  <div class="items-view--container">
    <div>
        <input 
            v-model="searchTerm" placeholder="Search for an item" data-testid="search-input" id="searchbox" 
        />
    </div>
    <ul>
      <li v-for="item in searchResults" :key="item.id">
        <h2>{{item}}</h2>
      </li>
    </ul>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

And our script setup is as follows:

import { useItems } from "../composables/useItems";
import PizzaCard from "../components/PizzaCard.vue";
import { useSearch } from "../composables/useSearch";

const props = defineProps({
  intialSearchTerm: {
    type: String,
    default: "",
  },
});

const { items } = useItems();

const { search, searchResults } = useSearch({
  items: items,
  defaultSearch: props.intialSearchTerm,
});
Enter fullscreen mode Exit fullscreen mode

We will map the ItemsView to the path /items, by editing the routes inside router/index.js file:

export const routes = [
//...
    {
        path: "/items",  
        component: ItemsView,
        name: 'items',
    }
];
//...
Enter fullscreen mode Exit fullscreen mode

Upon running and redirecting to /items, the browser will display the view with a search input and the initial list of items, as seen in the below screenshot.

ItemsView component

Next we will save the search term to the URL and test this functionality.

Save search term with Route URL query params

Within ItemsView.vue, we will import useRouter() from the vue-router package, and update the URL query params whenever the search term changes using watch():

/**ItemsView.vue */
import { watch } from "vue";
import { useRouter } from "vue-router";

const router = useRouter();

watch(search, (value, prevValue) => {
  if (value === prevValue) return;
  router.replace({ query: { search: value } });
});
Enter fullscreen mode Exit fullscreen mode

We also receive the component's initial search state from the URL, by adding the following code to the router/index.js and map the route's query params with the props of the ItemsView component:

export const routes = [
//...
    {
        path: "/items",  
        //...
        props: (route) => ({
          intialSearchTerm: route.query?.search || "",
        }), 
    }
];
Enter fullscreen mode Exit fullscreen mode

At this point whenever the user changes the search term and refreshes the browser, the component will remain in the same state, and won't reset the results to the default list.

Let's test it next.

Test the sync between search term and URL query

For this test scenario, we need to enable and wrap the component with routing, before mounting it. To do so, we add the following code in /playwright/index.js:

import { beforeMount } from '@playwright/experimental-ct-vue/hooks';
import router from '../src/router'

beforeMount(async ({ app }) => {
    app.use(router);
 });
Enter fullscreen mode Exit fullscreen mode

Playwright provides an app instance that we can hook a mock router to it. In our demo, for simplicity we will use the same router instance from router/index.js.

Note: This set up with beforeMount() will apply to all E2E tests in the project. At the time of writing this post, Playwright team hasn't provided the option to configure beforeMount per test yet.

Once done, we can add the check for the URL reflects the new search term to our should update the search term test case:

test("should update the search term", async ({ mount, page }) => {
    //...
    await expect(page.url()).toContain('/?search=hello')
})
Enter fullscreen mode Exit fullscreen mode

That's it! We have successfully tested the sync between the search term and the URL query params.

Debugging Playwright tests

You can debug your tests by add the --debug flag the test command (yarn test-ct --debug). With this flag, Playwright will open the browser's DevTools, pause the tests, and allow you to inspect the component's DOM while running the tests.

Playwright test run in debug mode

Summary

At this point, you should have a basic understanding of how to test a component that uses Vue Router to synchronize its states with the URL params, using Playwright. It will be more challenging to validate the query params synchronization in the same test scenario with Vue Test Utils, due to the limitation non browser's environment.

So when should you use Playwright for component testing? And when should you use traditional unit testing frameworks like Vue Test Utils or Testing Library? That depends on the complexity of your codebase, each component's nature and your test goals!

πŸ‘‰ Learn about Vue 3 and TypeScript with my new book Learning Vue!

πŸ‘‰ If you'd like to catch up with me sometimes, follow me on X | LinkedIn.

Like this post or find it helpful? Share it πŸ‘‡πŸΌ πŸ˜‰

Top comments (0)