Skip to content

Vue Router test utils #152

Closed
Closed
@lmiller1990

Description

@lmiller1990

In writing some small apps and the VTU docs, I encounted some caveats when testing components using Vue Router. This is mainly because the navigation is now all async (which I think it is a great design decision, it fits into Vue's async update model perfectly).

This happens because navigations are all asynchronous now.

When I first tried writing some tests using Vue Router, I didn't realize I had to do await router.isReady, or the initial navigation may not be completed before the component renders.

Also, I triggered a click on a <router-link> I got an error. It turns out that you need to wait for the navigation to complete (makes sense with the new async navigation model).

Here is a full snippet demonstrating what is currently required when using VueRouter in a jsdom environment (sans VTU).

it('avoids several caveats to succesfully click an <a> tag", async () => {
  const el = document.createElement('div')
  el.id = 'app'
  document.body.appendChild(el)
  const app = createApp(App)
  app.use(router)

  router.push('/')
  await router.isReady() // <- need to do this

  app.mount(el)

  const event = document.createEvent('Event')
  event.initEvent('click')
  let promise = new Promise(resolve => {
    const remove = router.afterEach(() => {
      remove() // <- need to do this
      resolve() // <- need to do this
    })
  })
  document.getElementById('post')!.dispatchEvent(event)
  await promise // <- need to do this
  console.log(document.body.outerHTML)
})

The user must await isReady, and also await resolve using the afterEach callback to ensure the navigation triggered when clicking on the <a> tag (from <router-link>) before continuing their tests. Kind of like how you need to do await nextTick for Vue to update the DOM.

We should bundle some goodies to make testing Vue Router a more smooth experience. @posva suggested await routerMock.pendingNavigation(). I think we need both good utils for testing with the actual VueRouter, and a mocked VueRouter.

This is the kind of thing you would previously write in VTU 1.x with Vue 2.x and Vue Router 3.x:

const router = createRouter()

const wrapper = mount(App. {
  global: {
    plugins: [router]
  }
})

await wrapper.find('a').trigger('click') // it "just works"

I see a few options, but I think the best and most simple is some additional methods on wrapper. Here are some ideas I had:

// react testing library style, like act(() => ...
await doNavigation(() => {
  wrapper.find('a').trigger('click')
})

// or a more VTU-like like API
await wrapper.navigate(wrapper.find('a'))

// or with a sneaky implicit find/findComponent
await wrapper.navigate('a')

I like the latter the best. Tight integration with Vue Router, and I think it's really readable.

Any ideas or feedback would be great 👍

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions