6

I am unable to mount a component during unit testing due to the the route object being undefined in the setup method during mounting. The guides seem aimed at Vue2 and the options API

References:

How to write test that mocks the $route object in vue components
How to unit testing with jest in vue composition api component?
https://vue-test-utils.vuejs.org/guides/#using-with-typescript
https://vue-test-utils.vuejs.org/guides/#using-with-vue-router

Error

● CoachItem.vue › displays alert when item is clicked

    TypeError: Cannot read property 'path' of undefined

      64 |       fullName: computed(() => props.firstName + " " + props.lastName),
      65 |       coachContactLink: computed(
    > 66 |         () => route.path + "/" + props.id + "/contact"

// @/tests/unit/example.spec.ts

import CoachItem from "@/components/coaches/CoachItem.vue"
import router from "@/router"


  describe("CoachItem.vue", () => {
    it("displays alert when item is clicked", async () => {

      //const route = { path: 'http://www.example-path.com' }
      router.push('/')
      await router.isReady()
      const wrapper = mount(CoachItem); //adding this line causes failure
      //await wrapper.trigger('click');
      //const dialog = wrapper.find('dialog');
      //(dialog.exists()).toBeTruthy()
    })
  })
// @/components/UserAlert.vue

<template>
  <div class="backdrop" @click="closeDialog"></div>
  <dialog open>
    <header>
      <h2>{{ title }}</h2>
    </header>
    <div>
      <slot name="content"></slot>
    </div>
    <menu>
      <button @click="closeDialog">Close</button>
    </menu>
  </dialog>
</template>

<script lang="ts>
import { defineComponent } from "vue";

export default defineComponent({
  props: ['title'],
  emits: ['close'],
  setup(_, context) {
    function closeDialog() {
      context.emit('close');
    }

    return { closeDialog };
  },
});
</script>
// @/components/coaches.CoachItem.vue

<template>
<user-alert v-if="alertIsVisible" title="Alert!" @close="hideAlert">
    <template v-slot:content><p>this is a slot</p></template>
  </user-alert>
  <li @click="showAlert">
    <h3>{{ fullName }}</h3>
    <h4>${{ rate }}/hour</h4>
    <div>
      <base-badge
        v-for="area in areas"
        :key="area"
        :type="area"
        :title="area"
      ></base-badge>
    </div>
    <div class="actions">
      <base-button mode="outline" link :to="coachContactLink"
        >Contact</base-button
      >
      <base-button link :to="coachDetailsLink">View Details</base-button>
    </div>
  </li>
</template>

<script lang="ts">
import { computed, defineComponent, PropType, ref } from "vue";
import { useRoute } from "vue-router";
import useAlert from "../../components/hooks/useAlert";
export default defineComponent({
  props: {
    id: {
      type: String,
      required: true,
    },
    firstName: {
      type: String,
      required: true,
    },
    lastName: {
      type: String,
      required: true,
    },
    rate: {
      type: Number,
      required: true,
    },
    areas: {
      type: Object as PropType<Array<string>>,
      required: true,
    },
  },
  setup(props) {
    const route = useRoute();
    const alertTitle = ref("delete user?");
    return {
      fullName: computed(() => props.firstName + " " + props.lastName),
      coachContactLink: computed(
        () => route.path + "/" + props.id + "/contact"
      ),
      coachDetailsLink: computed(() => route.path + "/" + props.id),
      ...useAlert()
    };
  },
});
</script>
// @/main.ts
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import {store, key }  from "./store";
import UserAlert from "@/components/UserAlert.vue";

createApp(App)
.component('UserAlert', UserAlert)
  .use(store, key)
  .use(router)
  .mount("#app");
// @/router/index.ts

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
});

export default router;
2
  • Those documentation links you share belongs to Vue2.x you need to check this documents. Commented Jan 6, 2021 at 9:31
  • @Eldar Those Vue router examples for Vue3.x seem to use the options API as well unfortunately Commented Jan 6, 2021 at 10:25

1 Answer 1

1

Going via the examples in vitest issue #1918 and the vue-test-utils composition documentation

The following mock allows a component with useRouter or useRoute to work:

import { mount } from '@vue/test-utils'
import { expect, it, vi } from 'vitest'
import CompWithRoute from './CompWithRoute.vue'

vi.mock('vue-router', () => {
  return {
    useRouter: vi.fn(() => ({
      push: vi.fn(),
    })),
    useRoute: vi.fn(()=> ({
      fullPath: '',
      hash: '',
      matched: [],
      meta: {},
      name: undefined,
      params: {},
      path: '/guppies',
      query: {
        search: 'ireland'
      },
      redirectedFrom: undefined,
    }))
  }
})

it('should render the route loving component', () => {
  const wrapper = mount(CompWithRoute)
})

If the call needs to be tested, mockImplementationOnce can inject a spy (although typescript doesn't like the lax implementation of the Router mock)

import { mount } from '@vue/test-utils'
import { afterEach, expect, it, vi } from 'vitest'
import * as routerExports from 'vue-router'
import CompWithRoute from './CompWithRoute.vue'

const useRouter = vi.spyOn(routerExports, 'useRouter')

afterEach(() => {
  vi.clearAllMocks()
})

it('should push a new route on search', async () => {
  const push = vi.fn()
  useRouter.mockImplementationOnce(() => ({
    push
  }))
  const wrapper = mount(CompWithRoute)
  const search = wrapper.find('#search-input')
  await search.setValue('ireland')
  await search.trigger('keyup.enter')
  expect(push).toHaveBeenCalledWith({ query: { search: 'ireland'} })
})

It's also possible to inject a global spy into the hoisted mock Router implementation and reference that in your expectations.

const mock_push = vi.fn()
vi.mock('vue-router', () => ({
  useRouter: vi.fn(() => ({
    push: mock_push,
  })),
}))

afterEach(() => {
  vi.clearAllMocks()
})

it('should push a new route on search', async () => {
  const wrapper = mount(CompWithRoute)
  const search = wrapper.find('#search-input')
  await search.setValue('ireland')
  await search.trigger('keyup.enter')
  expect(mock_push).toHaveBeenCalledWith({ query: { search: 'ireland'} })
})
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.