Vue-test-utils: vue-router is shared between tests

Created on 7 Sep 2020  Â·  8Comments  Â·  Source: vuejs/vue-test-utils

Subject of the issue

I have the following test:

import { createLocalVue, mount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'
import VueRouter, { RouteConfig } from 'vue-router'

const routes: RouteConfig[] = [
  {
    path: '/',
    name: 'Home',
  },
  {
    path: '/about',
    name: 'About',
  }
];

describe('HelloWorld.vue', () => {
  it('test one', () => {
    const localVue = createLocalVue()
    localVue.use(VueRouter)
    const router = new VueRouter({routes})

    const msg = 'new message'
    const wrapper = mount(HelloWorld, {
      localVue,
      router,
      propsData: { msg }
    })

    console.log("TEST ONE", wrapper.vm.$route.path);
    wrapper.vm.$router.replace("/whatever");
    console.log("TEST ONE", wrapper.vm.$route.path);

    expect(wrapper.text()).toMatch(msg)
  })

  it('test two', () => {
    const localVue = createLocalVue()
    localVue.use(VueRouter)
    const router = new VueRouter({routes})

    const msg = 'new message'
    const wrapper = mount(HelloWorld, {
      localVue,
      router,
      propsData: { msg }
    })

    console.log("TEST TWO", wrapper.vm.$route.path);

    expect(wrapper.text()).toMatch(msg)
  })
})

If you run it, you will see that it prints this to the console:

 PASS  tests/unit/example.spec.ts
  HelloWorld.vue
    √ test one (76ms)
    √ test two (11ms)

  console.log tests/unit/example.spec.ts:29
    TEST ONE /

  console.log tests/unit/example.spec.ts:31
    TEST ONE /whatever

  console.log tests/unit/example.spec.ts:48
    TEST TWO /whatever

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        5.646s, estimated 7s
Ran all test suites.

This means that the router instance is shared between tests, even though I am using separate localVue and router for both tests.

IMO tests should be independent -> they should have their own instance of vue-router. Is it possible with library? If you follow the examples in the docs, then you will end up with shared vue-router instance between tests.

Steps to reproduce

Just run unit tests in this project (npm run test:unit)

testing-vue-router.zip

Expected behaviour

All tests should have their own instance of vue-router

Actual behaviour

vue-router instance is shared between tests

Most helpful comment

Ok I remember this now. Turns out I actually found a solution for this. I wrote about it in my handbook.

You need to pass abstract (or history may work too).

    const localVue = createLocalVue()
    localVue.use(VueRouter)
    const router = new VueRouter({routes, mode: 'abstract' })

I don't think this is something VTU will be doing automatically. I'd say the only solution is to document this better.

This should solve your problem! Let me know if it doesn't.

All 8 comments

I would say this is not a vue-test-utils thing to worry about. You may want to try jest.resetModules().

Each test should have it's own instance of Vue Router using this strategy.

If you do router.push do you have the same problem? I wonder what wrapper.vm.$router.replace("/whatever") does internally. Is it possible this is changing the actual location.href (which would be part of jsdom, outside the control of VTU).

@lmiller1990

If you do router.push do you have the same problem?

Yes, If I replace router.replace with router.push, the same problem exists.

Ok I remember this now. Turns out I actually found a solution for this. I wrote about it in my handbook.

You need to pass abstract (or history may work too).

    const localVue = createLocalVue()
    localVue.use(VueRouter)
    const router = new VueRouter({routes, mode: 'abstract' })

I don't think this is something VTU will be doing automatically. I'd say the only solution is to document this better.

This should solve your problem! Let me know if it doesn't.

@lmiller1990
Thanks, it is working. However it works only for "abstract" mode. Why is this only working for "abstract" mode and not for the others? Could you explain this? Your handbook does not explain this.

Additionally, the handbook contains wrong information:
image

For 'history' mode it is not working. You can check this on my repro project.

I will make a PR to my book to fix that typo - it should be abstract and maybe hash (need to check).

I am not 100% sure why history has this issue. My guess is that the url is changed in one test, and resetting the url is outside the scope and control of Vue Test Utils, so even if you make a new router it will automatically grab the url from the current location, which is effectively a global variable that has been mutated. I could be completely off.

I would need to look into Vue Router in more depth to give you a definite answer, I do not have the time to do this right now though. If you figure it out please let me know and submit a PR to the handbook with more info.

@lmiller1990
I figured out why it is working like that:

In the history mode vue-router sets the initial state by using window.location.pathname
https://github.com/vuejs/vue-router/blob/ecc8e27e5d3e7270d6a7942539d2f7d96308d5cd/src/history/html5.js#L88

In the hash mode vue-router sets the initial state by using window.location.href and extracts the value after hash from url
https://github.com/vuejs/vue-router/blob/ecc8e27e5d3e7270d6a7942539d2f7d96308d5cd/src/history/hash.js#L118

And of course window.location is a global variable which is outside of the scope and control of VTU.

However, in the abstract mode, the navigation is based on the stack and this stack is always resetted when a new instance of vue-router is created
https://github.com/vuejs/vue-router/blob/ecc8e27e5d3e7270d6a7942539d2f7d96308d5cd/src/history/abstract.js#L13

This is why only the abstract mode can ensure creating new, fresh instances of vue-router.

I was kind of correct then!

I'll add that information to my book. Thanks @Mikilll94.

Was this page helpful?
0 / 5 - 0 ratings