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

时间:2017-01-04 07:51:10

标签: unit-testing sinon vue-component vuejs2 vue-loader

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

12 个答案:

答案 0 :(得分:30)

最好不要模拟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. 在您查看/测试HTML之前,您需要nextTick才能呈现HTML。
  4. 其中一个问题是我发现的大多数示例都提到旧版vue-router,请参阅the migrations docs,例如。一些例子使用router.go()现在不起作用。

答案 1 :(得分:22)

我不同意最常见的答案 - 你可以毫无问题地模仿$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')

答案 2 :(得分:3)

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

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

我们创建了一个示例vue实例。在测试时,您需要使用shallowMount,以便提供vue应用实例和路由器。

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

答案 3 :(得分: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别人!

答案 4 :(得分: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)
    });
});

希望有帮助!

答案 5 :(得分: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

答案 6 :(得分: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>`
    )
  })
})

答案 7 :(得分: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');
});

答案 8 :(得分: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

答案 9 :(得分: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)
})

答案 10 :(得分:0)

您不必专门“模拟”路由器。您的应用程序可以在全局vue范围内设置VueRouter,并且仍然可以使其在测试中按您的意愿进行操作,而不会出现问题。

通过VueRouterhttps://vue-test-utils.vuejs.org/guides/#using-with-vue-router读取localVue用法。

我目前正在从我们的主应用中拉入一个复杂的路由器,并且能够jest.spyOn()调用router.push(),并且可以在某些组件运行shallowMount()之前创建组件之前设置路径created()挂钩中进行路由处理。

解决方法

// someVueComponent.vue

<template>
... something
</template>
<script>
...
data () {
  return {
    authenticated: false
  }
},
...
created () {
  if(!this.authenticated && this.$route.path !== '/'){
    this.$router.push('/')
  }
}
</script>

// someVueComponent.spec.js

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

在撰写本文时,似乎存在一个开放的缺陷,该缺陷使以下各项无法正常工作,因为vm.$route始终是未定义的,因此上述(我知道)是唯一的选择,因为没有其他方法可以“模拟” $route,因为安装VueRouter会将只读属性写入$route

来自vue-test-utils文档https://vue-test-utils.vuejs.org/guides/#mocking-route-and-router

import { shallowMount } from '@vue/test-utils'

const $route = {
  path: '/some/path'
}

const wrapper = shallowMount(Component, {
  mocks: {
    $route
  }
})

wrapper.vm.$route.path // /some/path

如果您对此处感兴趣的是复制该问题的github链接:https://github.com/vuejs/vue-test-utils/issues/1136

答案 11 :(得分:0)

为什么所有答案都这么复杂?您可以这样做:

 [echo] URL value is https://www.google.com