Skip to content

vue-router is shared between tests #1681

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Mikilll94 opened this issue Sep 7, 2020 · 10 comments
Closed

vue-router is shared between tests #1681

Mikilll94 opened this issue Sep 7, 2020 · 10 comments

Comments

@Mikilll94
Copy link

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

@winniehell
Copy link
Contributor

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

@lmiller1990
Copy link
Member

lmiller1990 commented Sep 10, 2020

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).

@Mikilll94
Copy link
Author

@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.

@lmiller1990
Copy link
Member

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.

@Mikilll94
Copy link
Author

Mikilll94 commented Sep 12, 2020

@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.

@lmiller1990
Copy link
Member

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.

@Mikilll94
Copy link
Author

Mikilll94 commented Sep 12, 2020

@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.

@lmiller1990
Copy link
Member

I was kind of correct then!

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

@agentdon
Copy link

Ran into this problem just now and in addition to using abstract mode, I also had to create a function to build a new router instance each time I render my component. That might have been how you all had it setup, but wanted to leave this as a comment just in case anyone else trips up on this like I did. My solution basically looks like:

return render({ component: SomeComponent, router: buildRouter() })
// ...
const buildRouter = () => {
  return new VueRouter({
    mode: 'abstract',
    routes,
  })
}

@lmiller1990
Copy link
Member

I wrote a bit about this in a little book I'm writing (freely available): https://cypress-testing-handbook.netlify.app/vue-router.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants