Search code examples
vue.jsvue-routervitest

How to unit test vue beforeRouteUpdate hooks


I have the following vue component, which uses onBeforeRouteUpdate hook to call the init() method when the route param changes (test/a -> test/b).

The following stackblitz instance shows the output using npm run test:unit. https://stackblitz.com/~/github.com/demiglace0505/sandbox.

<script setup>
const store = useMyStore()

const init = async () => {
  console.log('init call')
  store.setFlags()
}

onBeforeRouteUpdate((to, from, next) => {
  console.log('calling onBeforeRouteUpdate')
  if (to.name === from.name) {
    next()
    init()
  }
})
</script>

Here is the unit test:

test('setFlags() is called when transferring from a to b', async () => {
  const pinia = createPinia()
  setActivePinia(pinia)
  store = useMyStore()
  store.setFlags = vi.fn()
  
  const router = createRouter({
    history: createMemoryHistory(),
    routes
  })

  // initial screen: a
  router.push('/test/a')
  await router.isReady()

  mount(TestComponent, {
    global: {
      plugins: [router]
    }
  })

  // simulate a router update from a to b
  router.push('/test/b')
  await router.isReady()
  await flushPromises()

  expect(store.setFlags).toHaveBeenCalledOnce()
})

The problem is, onBeforeRouteUpdate() doesn't seem to get triggered. The console.logs are not even getting written. I am also getting the following message, only on the test console.

No active route record was found when calling 'onBeforeRouteUpdate()'

In the browser however, the app behaves normally.


Solution

  • The output suggests what's wrong:

    [Vue Router warn]: No active route record was found when calling onBeforeRouteUpdate(). Make sure you call this function inside a component child of . Maybe you called it inside of App.vue?

    There's no router view, so the router doesn't work.

    It should be:

    wrapper = mount(() => h('div', {}, [h(RouterView), h(TestComponent)]), {...});
    

    There is no need for await router.isReady() on route update, it's responsible for initial navigation. But there should be await flushPromises() in order for route update to be applied.

    In case this is unit test, there's a problem with testing methodology, as it tests how multiple units work together, making it integration test. In a proper unit test, vue-router module is supposed to be mocked.