Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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! 🙇🏽

like image 551
Zhave Avatar asked May 30 '18 21:05

Zhave


2 Answers

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
});
like image 185
Ankit Kante Avatar answered Oct 09 '22 11:10

Ankit Kante


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();
like image 22
ferdynator Avatar answered Oct 09 '22 09:10

ferdynator