8

I'm trying to understand the logic of mocking the Vue-Router with Vitest.

For this, I tried to set up and mock my test environment on a very simple project. When I tried to proceed according to the official documentation of Vue-Test-Utils, I always got an error. I don't know if it's because they use Jest.

Using real vue-router solves my problem but I think it's better to mock vue-router.

Below, I first convey the source codes of the project, and then the error I received.

Home.vue

<script setup lang="ts">
import {onMounted} from "vue";
import {useRoute} from "vue-router";

const route = useRoute()

onMounted(() => {
  console.log(route.query)
})
</script>

<template>
  <div>Home</div>
</template>

Home.spec.ts

import {expect, it, vi} from "vitest";
import {mount} from "@vue/test-utils";

import Home from "../src/components/Home.vue"

it('Home Test', async () => {
    const wrapper = mount(Home)

    expect(wrapper.exists()).toBeTruthy()
})

vite.config.ts

/// <reference types="vitest" />
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  test: {
    environment: 'jsdom',
    include: ['./test/**/*.spec.ts'],
    exclude: ['node_modules', 'dist'],
    globals: true
  }
})

My error message is as follows:..

Error Message 1

Error Message 2

Methods I've Tried

I tried to mock vue-router like below

vi.mock('vue-router', () => ({
    useRoute: vi.fn(),
}))

or just

vi.mock('vue-router')

Here is my final Home.spec.ts file

import {expect, it, vi} from "vitest";
import {mount} from "@vue/test-utils";

import Home from "../src/components/Home.vue"

vi.mock('vue-router')

it('Home Test', async () => {
    const wrapper = mount(Home, {
        global: {
            stubs: ["router-link", "router-view"]
        }
    })

    expect(wrapper.exists()).toBeTruthy()
})

4 Answers 4

4

First, I expected to see the router-link or router-view in Home.vue:

<script setup lang="ts">
import { onMounted } from 'vue';
import { useRoute } from 'vue-router';

const route = useRoute();

onMounted(() => {
  console.log(route.query);
});
</script>

<template>
  <router-link to="home">Go to home</router-link>
  <router-view />
</template>

So, the Home.spec.ts should be something like this:

import { expect, it, vi } from 'vitest';
import { mount } from '@vue/test-utils';
import * as VueRouter from 'vue-router';
import Home from '../src/components/Home.vue';

describe('./path/to/Home.vue', () => {
  const useRouteSpy = vi.spyOn(VueRouter, 'useRoute');
  const getWrapper = () => mount(Home as any, {
    global: {
      stubs: {
        'router-link': { template: '<div/>' },
        'router-view': { template: '<div/>' },
      },
    },
  });

  it('the component should be mounted', () => {
    // ARRANGE
    const useRouteMock = useRouteSpy.mockImplementationOnce({ query: 'query' });
    // ACT
    const wrapper = getWrapper();
    // ASSERT
    expect(useRouteMock).toHaveBeenCalled();
    expect(wrapper.exists()).toBeTruthy();
  });
});

Some comments/words of advice:

  • Use describes to boundary the test context
  • Define a global function to mount the component, reuse instead of repeat
  • Use .spyOn() and .mockImplementation...() to spy and mock
  • Use some structured/straightforward way to write your test, like AAA [arrange, act, assert] or GWT [given, when, then]. I have been testing for a few years and still using it, it helps me to understand what I'm testing
  • Use .toHaveBeenCalled...() to check if the mock worked as expected
  • The stubs in the mount() function should be related with the components used in the template (so, if you're not using <router-view>, it should not be listed as a stub)

Hope that helps, Cheers!

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you very much for your suggestions.. :) I especially liked the AAA system. I have implemented your codes but this time I am getting an error like this "TypeError: Cannot redefine property: useRoute". In the line where we spy the VueRouter
I would say that something in your typescript config is messing with vitest. You can check how the .spyOn() syntax works in the vitest or jest docs
@Mocha_ Did you find a solution to the error "TypeError: Cannot redefine property: useRoute"? I have the same issue
@Steve I'm sorry for late reply, after this error i started using "vue-router-mock" package. It works well for component or page tests, but I haven't used it for composable tests yet.
1

in case anyone need to do the same thing, now you can easily mock the vue-router using vue-router-mock

// for example
import { getRouter } from 'vue-router-mock'

test('should move to /dashboard', () => {
 const route = getRouter()
 wrapper.router.push('/dashboard')
 console.log(route.currentRoute.value.fullPath) // /dashboard
})

Comments

0

The simplest approach I could come up with looks like this:

In my production code I try to access a variable like this

route.params.entityId

which yields an error like yours in my test code.

With a setup like this:

import {useRoute} from "vue-router";
   
vi.mock('vue-router')
    
describe('MainPanelEntityPage', () => {

  useRoute.mockReturnValue({
    params: {
      entityId: "17"
    },
  })

  it('should be mounted', async () => {
    const wrapper = mount(MainPanelEntityPage)
     ...
  }
}

I am able to retrieve "17" as entityId from the mocked route params.

Hope that helps.

Comments

0

i have almost the same confusion as yours, after hundred and thousands of try, i figure out it:

the official website of vitest has the following warning in chapter vi.mock: Vitest will not mock modules that were imported inside a setup file because they are cached by the time a test file is running. You can call vi.resetModules() inside vi.hoisted to clear all module caches before running a test file.

so, if you are using setup, you need to call vi.resrtModules() first before using vi.mock(), for example:

vi.hoisted(() => {
  vi.resetModules()
})

vi.mock('vue-router', async (importOriginal) => {
    const actual = await importOriginal<typeof import('vue-router')>()
    return {
      ...actual,
      useRoute: vi.fn(),
     }
})

now it will work

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.