vue-testing-library: VTL for Vue 3: The routes option does not work correctly

Describe the bug When using the routes option of the render function, it throws up some warnings that have to do with the _history property and the asynchronic handling that vue router 4 has. This causes you to not be able to use the routes option correctly.

To Reproduce

import { render, screen } from '@testing-library/vue'
import '@testing-library/jest-dom'
import { defineComponent } from 'vue'
import { RouteRecordRaw } from 'vue-router'

test('Component with route', () => {
  const ComponentA = defineComponent({
    name: 'ComponentA',
    props: {
      to: {
        type: Object,
        required: true
      }
    },
    template: `<router-link :to="to" role="button">Learn More</router-link>`
  })

  const ComponentB = defineComponent({
    name: 'ComponentB'
  })

  const routeRecordRaw: RouteRecordRaw = {
    path: '/',
    name: 'componentB',
    component: ComponentB
  }

  const to = { name: routeRecordRaw.name }

  render(ComponentA, {
    props: { to },
    routes: [routeRecordRaw]
  })

  const button = screen.getByRole('button')

  expect(button).toBeInTheDocument()
  expect(button?.getAttribute('href')).toBe(routeRecordRaw.path)
})

This throws the following warning

console.warn node_modules/vue-router/dist/vue-router.cjs.js:75
    [Vue Router warn]: Unexpected error when starting the router: TypeError: Cannot read property '_history' of null

The warning is known. This is explained in the vue-test-utils v2 documentation https://vue-test-utils.vuejs.org/v2/guide/vue-router.html#with-a-real-router

So to solve it apply what is indicated in documentation

import { render, screen } from '@testing-library/vue'
import '@testing-library/jest-dom'
import { defineComponent } from 'vue'
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'

test('Component with route', async () => {
  const ComponentA = defineComponent({
    name: 'ComponentA',
    props: {
      to: {
        type: Object,
        required: true
      }
    },
    template: `<router-link :to="to" role="button">Learn More</router-link>`
  })

  const ComponentB = defineComponent({
    name: 'ComponentB'
  })

  const routeRecordRaw: RouteRecordRaw = {
    path: '/',
    name: 'componentB',
    component: ComponentB
  }

  const router = createRouter({
    history: createWebHistory(process.env.BASE_URL),
    routes: [routeRecordRaw]
  })

  router.push('/')
  await router.isReady()

  const to = { name: routeRecordRaw.name }

  render(ComponentA, {
    props: { to },
    global: {
      plugins: [router]
    }
  })

  const button = screen.getByRole('button')

  expect(button).toBeInTheDocument()
  expect(button?.getAttribute('href')).toBe(routeRecordRaw.path)
})

Expected behavior Be able to correctly use the routes option provided by the render function

Related information:

  • @testing-library/vue version: ^6.3.1
  • Vue version: ^3.0.0
  • node version: 14.4.0
  • npm version: 6.14.9

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 25 (16 by maintainers)

Most upvoted comments

I have definitely seen the _history error before. It was related to awaiting the router to be read, but you’ve already done that.

I wonder if this is a bug here? I feel like it might be either in Test Utils or something else. I wonder if we can reproduce it without Testing Library (VTU + Jest). I wonder if you need to have a top level component (like <app>) with a <router-view /> for this to work as expected.

Going back to this. As I see it, we’re down to two options:

  1. Make render async when routes is present, and then call router.isReady() internally. API change: you’d need to await render(Comp, { routes }), but not when routes is not there. We could warn users somehow (plus, TS types would help, too).

  2. Remove vuex/router integration for Vue 3, and leave it to users. This would make users responsible for setting up router (and store) and call router.isReady(), which is an implementation detail (even though it is on the Arrange section of tests, which is OK, I guess), and would keep render sync. This solution makes VTL more aligned to other Testing Lib flavors.

To be fair, setting up your own router/vuex isn’t that bad now with VTU 2 since there’s no need to set up a local vue instance. On the other hand, “hiding” the need to mke router ready feels… right for users.

I thought about my previous comment, it’s a bad idea, actually. We did something similar for Vue 2 (“sync mode”) and it had a lot of knock on effects where tests vs the real thing were different. We had both “sync mode” for test utils and something in Vue core.

And with the mock router, I guess XTL philosophy could be a problem. I assume the preference would be to use the real router where possible, but that brings things back to square one.

If we don’t want render to be async then we will not be able to do this for the user. I suppose we just expose a method and document it well.

Maybe we should just expose the entire router - that seems useful. Eg:

const {router} = render(ComponentA, {
  props: {to},
  routes: [routeRecordRaw],
})
await router.isReady()

Although this may not fit with the VTL philosophy. I think this idea (expose some kind of router key) is probably the way to go.