如何编写在vue组件中模拟$ route对象的测试

问题描述 投票:21回答:11

我有一个包含像this.$route.fullPath这样的语句的组件,如果我想测试那个组件,我应该如何模拟fullPathof $route对象的值?

unit-testing sinon vue-component vuejs2 vue-loader
11个回答
32
投票

最好不要模拟vue-router,而是使用它来渲染组件,这样你就可以得到一个正常工作的路由器。例:

import Vue from 'vue'
import VueRouter from 'vue-router'
import totest from 'src/components/totest'

describe('totest.vue', () => {
  it('should totest renders stuff', done => {
    Vue.use(VueRouter)
    const router = new VueRouter({routes: [
        {path: '/totest/:id', name: 'totest', component: totest},
        {path: '/wherever', name: 'another_component', component: {render: h => '-'}},
    ]})
    const vm = new Vue({
      el: document.createElement('div'),
      router: router,
      render: h => h('router-view')
    })
    router.push({name: 'totest', params: {id: 123}})
    Vue.nextTick(() => {
      console.log('html:', vm.$el)
      expect(vm.$el.querySelector('h2').textContent).to.equal('Fred Bloggs')
      done()
    })
  })
})

注意事项:

  1. 我正在使用仅运行时版本的vue,因此render: h => h('router-view')
  2. 我只测试totest组件,但如果它们被totest引用,则可能需要其他组件,例如。在这个例子中another_component
  3. 在您可以查看/测试之前,您需要nextTick来呈现HTML。

其中一个问题是我发现的大多数例子都提到了旧版本的vue-router,参见the migrations docs,例如。一些例子使用router.go()现在不起作用。


0
投票

我找到的最简单的方法是模拟$ route。

it('renders $router.name', () => {
  const $route = {
    name: 'test name - avoriaz'
  }


 const wrapper = shallow(Component, {
    mocks: {
      $route
    }
  })
  expect(wrapper.text()).to.equal($route.name)
})

0
投票

你不必专门“模拟”路由器。您的应用程序可以在全局vue范围内设置VueRouter,您仍然可以使它在测试中完成您想要的操作而不会出现问题。

阅读localVue与qazxsw poi:qazxsw poi一起使用。

我正在从我们的主应用程序中引入一个复杂的路由器,并能够VueRouter调用https://vue-test-utils.vuejs.org/guides/#using-with-vue-router以及在创建组件之前设置路径运行jest.spyOn()以在router.push()钩子中进行某些路由处理。

解决方法

// someVueComponent.vue

shallowMount()

// someVueComponent.spec.js

created()

以上是在创建/装载<template> ... something </template> <script> ... data () { return { authenticated: false } }, ... created () { if(!this.authenticated && this.$route.path !== '/'){ this.$router.push('/') } } </script> 之前需要更改import Vuex from 'vuex' import VueRouter from 'vue-router' import { shallowMount, createLocalVue } from '@vue/test-utils' import SomeVueComponent from 'MyApp/components/someVueComponent' import MyAppRouter from 'MyApp/router' import MyAppCreateStore from 'MyApp/createStore' import merge from 'lodash.merge' function setVueUseValues (localVue) { localVue.use(Vuex) localVue.use(VueRouter) // other things here like custom directives, etc } beforeEach(() => { // reset your localVue reference before each test if you need something reset like a custom directive, etc localVue = createLocalVue() setVueUseValues(localVue) }) let localVue = createLocalVue() setVueUseValues(localVue) test('my app does not react to path because its default is "/"', () => { const options = { localVue, router: MyAppRouter, store: MyAppCreateStore() } const routerPushSpy = jest.spyOn(options.router, 'push') const wrapper = shallowMount(SomeVueComponent, options) expect(routerPushSpy).toHaveBeenCalledTimes(0) }) test('my app reacts to path because its not "/" and were not authenticated', () => { const options = { localVue, router: MyAppRouter, store: MyAppCreateStore() } const routerPushSpy = jest.spyOn(options.router, 'push') options.router.push('/nothomepath') expect(routerPushSpy).toHaveBeenCalledWith('/nothomepath') // <- SomeVueComponent created hook will have $route === '/nothomepath' as well as fullPath const wrapper = shallowMount(SomeVueComponent, options) expect(routerPushSpy).toHaveBeenCalledWith('/') // <- works }) 状态的想法。假设您可以创建包装器并希望根据其他状态或操作测试组件$route,您可以始终监视SomeVueComponent.vue实例

this.$router.push('/something')

在撰写本文时,似乎存在一个开放性缺陷,使得以下工作无法正常工作,因为wrapper.vm将始终未定义,使上述唯一选项(我知道),因为没有其他方法可以“模拟”let routerPushSpy = jest.spyOn(wrapper.vm.$router, 'push') // or before hooks, etc 因为安装VueRouter将只读属性写入vm.$route

从vue-test-utils docs $route

$route

如果你感兴趣的话是github链接到问题的再现:https://vue-test-utils.vuejs.org/guides/#mocking-route-and-router


24
投票

我不同意最好的答案 - 你可以毫无问题地模仿$route

另一方面,在基础构造函数上多次安装vue-router会导致问题。它将$route$router添加为只读属性。这使得在将来的测试中无法覆盖它们。

使用vue-test-utils有两种方法可以实现这一目标。

使用mocks option模拟vue-router

const $route = {
    fullPath: 'full/path'
}
const wrapper = mount(ComponentWithRouter, { 
  mocks: {
    $route
  } 
})

wrapper.vm.$route.fullPath // 'full/path'

您还可以使用createLocalVue安全地安装Vue Router:

使用createLocalVue在测试中安全地安装vue-router

const localVue = createLocalVue()
localVue.use(VueRouter)
const routes = [
 {
   path: '/',
   component: Component
 }
]
const router = new VueRouter({
 routes
})
const wrapper = mount(ComponentWithRouter, { localVue, router })
expect(wrapper.vm.$route).to.be.an('object')

3
投票

没有答案帮助我,所以我深入研究vue-test-utils文档,发现自己是一个有效的答案,所以你需要导入。

import { shallowMount,createLocalVue } from '@vue/test-utils';
import router from '@/router.ts';
const localVue = createLocalVue();

我们创建了一个示例vue实例。在测试时你需要使用shallowMount,这样你就可以提供vue app实例和路由器。

describe('Components', () => {
  it('renders a comment form', () => {
    const COMMENTFORM = shallowMount(CommentForm,{
      localVue,
      router
    });
  })
})

您可以轻松地将路由器传递到浅层安装,但它不会给您带来错误。如果您想通过商店,请使用:

import { shallowMount,createLocalVue } from '@vue/test-utils';
import router from '@/router.ts';
import store from '@/store.ts';
const localVue = createLocalVue();

然后通过商店:

describe('Components', () => {
  it('renders a comment form', () => {
    const COMMENTFORM = shallowMount(CommentForm,{
      localVue,
      router,
      store
    });
  })
})

该解决方案解决了以下错误:

  • 使用this.$route.params.id时无法读取未定义的属性'params'
  • 未知的自定义元素router-link


1
投票

感谢@SColvin的回答;帮我在我的场景中找到了答案,其中我有一个带有路由器链接的组件

ERROR: '[Vue warn]: Error in render function: (found in <RouterLink>)'

在单元测试期间,因为Vue没有配备路由器。使用@SColvin回答来重写最初由vue-cli提供的测试

describe('Hello.vue', () =>
{
  it('should render correct contents', () =>
  {
    const Constructor = Vue.extend(Hello);
    const vm = new Constructor().$mount();
    expect(vm.$el.querySelector('.hello h1').textContent)
      .to.equal('Welcome to Your Vue.js App');
  });

describe('Hello.vue', () =>
{
  it('should render correct contents', () =>
  {
    Vue.use(VueRouter);
    const router = new VueRouter({
      routes: [
        { path: '/', name: 'Hello', component: Hello },
      ],
    });
    const vm = new Vue({
      el: document.createElement('div'),
      /* eslint-disable object-shorthand */
      router: router,
      render: h => h('router-view'),
    });
    expect(vm.$el.querySelector('.hello h1').textContent)
      .to.equal('Welcome to Your Vue.js App');
  });
});

不需要将参数传递给视图我可以将组件简化为默认渲染,无需推送,也无需等待nextTick。 HTH别人!


1
投票

我发现最简单的方法是使用localVue

import { createLocalVue, mount } from '@vue/test-utils'
import ComponentName from 'componentPath'
import Vuex from 'vuex'
import store from '@/store/store' //Add store file if any getters is accessed
import VueRouter from 'vue-router'

describe('File name', () => { 
const localVue = createLocalVue()
localVue.use(VueRouter)
const routes = [  //Can also be rreplaced with route(router.js) file
    {
        path: '/path',
        component: ComponentName,
        name: 'Route name'
    }
]
const router = new VueRouter({
    routes
})
router.push({ 
              name: 'Route name',
              params: {} 
            }) //if needed
const wrapper = mount(ComponentName, {localVue, router, store })
beforeEach(function() {      
});

    it('Method()', () => {
        wrapper.vm.methodName()
        expect(wrapper.vm.$route.path).toBe(routes[0].path)
    });
});

希望能帮助到你!!!


0
投票

从@SColvin添加了很好的答案,这里有一个使用Avoriaz工作的例子:

import { mount } from 'avoriaz'
import Vue from 'vue'
import VueRouter from 'vue-router'
import router from '@/router'
import HappyComponent from '@/components/HappyComponent'

Vue.use(VueRouter)

describe('HappyComponent.vue', () => {
  it('renders router links', () => {
    wrapper = mount(HappyComponent, {router})
    // Write your test
  })
})

我相信这也适用于vue-test-utils


0
投票

使用vue-test-utils看一下这个例子,我在这里嘲笑路由器和商店。

import ArticleDetails from '@/components/ArticleDetails'
import { mount } from 'vue-test-utils'
import router from '@/router'

describe('ArticleDetails.vue', () => {
  it('should display post details', () => {
    const POST_MESSAGE = 'Header of our content!'

    const EXAMPLE_POST = {
      title: 'Title',
      date: '6 May 2016',
      content: `# ${POST_MESSAGE}`
    }

    const wrapper = mount(ArticleDetails, {
      router,

      mocks: {
        $store: {
          getters: {
            getPostById () {
              return EXAMPLE_POST
            }
          }
        }
      }
    })

    expect(wrapper.vm.$el.querySelector('h1.post-title').textContent.trim()).to.equal(EXAMPLE_POST.title)
    expect(wrapper.vm.$el.querySelector('time').textContent.trim()).to.equal(EXAMPLE_POST.date)
    expect(wrapper.vm.$el.querySelector('.post-content').innerHTML.trim()).to.equal(
      `<h1>${POST_MESSAGE}</h1>`
    )
  })
})

0
投票

根据this article,这就是我一直在做的事情:

it('renders $router.name', () => {
    const scopedVue = Vue.extend();

    const mockRoute = {
        name: 'abc'
    };

    scopedVue.prototype.$route = mockRoute;

    const Constructor = scopedVue.extend(Component);
    const vm = new Constructor().$mount();
    expect(vm.$el.textContent).to.equal('abc');
});

0
投票

您可以通过设置vm._routerRoot._router来模拟vm。$ router

例如

var Constructor      = Vue.extend(Your_Component)
var vm               = new Constructor().$mount()
var your_mock_router = {hello:'there'}

vm.$router             = your_mock_router //An error 'setting a property that has only a getter'
vm._routerRoot._router = your_mock_router //Wow, it works!

你可以在这里仔细检查他们的源代码:https://github.com/vuejs/vue-router/blob/dev/dist/vue-router.js#L558

© www.soinside.com 2019 - 2024. All rights reserved.