Vue-Router Mock With Vue-Test-Utils & Vitest
Asked Answered
I

4

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()
})
Idioblast answered 26/10, 2022 at 13:56 Comment(0)
H
3

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!

Hermanhermann answered 27/10, 2022 at 6:35 Comment(4)
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 VueRouterIdioblast
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 docsHermanhermann
@Idioblast Did you find a solution to the error "TypeError: Cannot redefine property: useRoute"? I have the same issueIdolatry
@Idolatry 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.Idioblast
D
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
})
Danyelldanyelle answered 2/11, 2023 at 10:26 Comment(0)
H
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.

Hellas answered 3/4, 2024 at 17:4 Comment(0)
B
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

Betel answered 28/5, 2024 at 2:24 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.