How to test VueRouter's beforeRouteEnter using '@vue/test-utils'?
Asked Answered
O

2

11

I'm trying to test my 'Container' component which handles a forms logic. It is using vue-router and the vuex store to dispatch actions to get a forms details.

I have the following unit code which isn't working as intended:

it('On route enter, it should dispatch an action to fetch form details', () => {
  const getFormDetails = sinon.stub();
  const store = new Vuex.Store({
    actions: { getFormDetails }
  });

  const wrapper = shallowMount(MyComponent, { store });
  wrapper.vm.$options.beforeRouteEnter[0]();
  expect(getFormDetails.called).to.be.true;
});

With the following component (stripped of everything because I don't think its relevant (hopefully):

export default {
  async beforeRouteEnter(to, from, next) {
    await store.dispatch('getFormDetails');
    next();
  }
};

I get the following assertion error:

AssertionError: expected false to be true

I'm guessing it is because I am not mounting the router in my test along with a localVue. I tried following the steps but I couldn't seem to get it to invoke the beforeRouteEnter.

Ideally, I would love to inject the router with a starting path and have different tests on route changes. For my use case, I would like to inject different props/dispatch different actions based on the component based on the path of the router.

I'm very new to Vue, so apologies if I'm missing something super obvious and thank you in advance for any help! 🙇🏽

Overby answered 30/5, 2018 at 21:29 Comment(0)
C
6

See this doc: https://lmiller1990.github.io/vue-testing-handbook/vue-router.html#component-guards

Based on the doc, your test should look like this:

it('On route enter, it should dispatch an action to fetch form details', async () => {
  const getFormDetails = sinon.stub();
  const store = new Vuex.Store({
    actions: { getFormDetails }
  });

  const wrapper = shallowMount(MyComponent, { store });
  const next = sinon.stub()

  MyComponent.beforeRouteEnter.call(wrapper.vm, undefined, undefined, next)

  await wrapper.vm.$nextTick()

  expect(getFormDetails.called).to.be.true;
  expect(next.called).to.be.true
});
Crenshaw answered 14/12, 2019 at 8:6 Comment(0)
D
3

A common pattern with beforeRouteEnter is to call methods directly at the instantiated vm instance. The documentation states:

The beforeRouteEnter guard does NOT have access to this, because the guard is called before the navigation is confirmed, thus the new entering component has not even been created yet.

However, you can access the instance by passing a callback to next. The callback will be called when the navigation is confirmed, and the component instance will be passed to the callback as the argument:

beforeRouteEnter (to, from, next) {
 next(vm => {
   // access to component instance via `vm`
 })
}

This is why simply creating a stub or mock callback of next does not work in this case. I solved the problem by using the following parameter for next:

// mount the component
const wrapper = mount(Component, {});

// call the navigation guard manually
Component.beforeRouteEnter.call(wrapper.vm, undefined, undefined, (c) => c(wrapper.vm));

// await 
await wrapper.vm.$nextTick();
// alternatively: as suggested by @furtivesock
// await flushPromises()
Deidradeidre answered 14/2, 2021 at 12:58 Comment(1)
If you're using the common pattern with asynchronous requests (e.g. data loading), you may use await flushPromises() instead of await wrapper.vm.$nextTick() in your test.Jamboree

© 2022 - 2024 — McMap. All rights reserved.