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.
Just run unit tests in this project (npm run test:unit)
All tests should have their own instance of vue-router
vue-router instance is shared between tests
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:

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.
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(orhistorymay work too).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.