Vue-test-utils

Vue-test-utils 是官方提供的内置在vue中的一个测试工具,它可以加载某个vue实例并可以提取,替换其中的方法,属性,事件等。

其实熟悉了Vue框架之后,再来看vue-test-util工具会很容易,因为二者之间的许多概念是相同的。

这篇博客基于vue-test-utils,vue的官方文档,对于部分官方文档中有歧义的地方进行了尝试,也参考了一些博客之后进行了解释。主要是用于概念的理解以及基本的几个用法的解释。

API

Mount

  • 参数

    • component: Component
    • options: Options
  • 返回值:Wrapper

  • options

    • context:Object

      将上下文传递给函数式组件,该选项只能用于函数式组件

      示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      import Foo from './Foo.vue'
      import Bar from './Bar.vue'
      const wrapper = mount(Component, {
      context: {
      props: { show: true },
      children: [Foo, Bar]
      }
      })
      expect(wrapper.is(Component)).toBe(true)
    • slots: Array|Component|string

      为组件提供一个 slot 内容的对象。该对象中的键名就是相应的 slot 名,键值可以是一个组件、一个组件数组、一个字符串模板或文本。

      示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      import Foo from './Foo.vue'

      const bazComponent = {
      name: 'baz-component',
      template: '<p>baz</p>'
      }

      const wrapper = shallowMount(Component, {
      slots: {
      default: [Foo, '<my-component />', 'text'],
      fooBar: Foo, // 将会匹配 `<slot name="FooBar" />`.
      foo: '<div />',
      bar: 'bar',
      baz: bazComponent,
      qux: '<my-component />'
      }
      })

      expect(wrapper.find('div')).toBe(true)

      这里官方文档的描述太过简单,可能会产生歧义,如果大家对这段内容有疑惑,可以查看这篇文章

    • scopedSlots

      提供一个该组件所有作用域插槽的对象。每个键对应到插槽的名字。

      你可以使用 slot-scope 特性设置 prop 的名称:

      1
      2
      3
      4
      5
      shallowMount(Component, {
      scopedSlots: {
      foo: '<p slot-scope="foo">{{foo.index}},{{foo.text}}</p>'
      }
      })

      否则插槽被计算的时候可以通过 props 对象使用 prop:

      1
      2
      3
      4
      5
      shallowMount(Component, {
      scopedSlots: {
      default: '<p>{{props.index}},{{props.text}}</p>'
      }
      })

      你也可以传递一个函数将 prop 作为参数带入:

      1
      2
      3
      4
      5
      6
      7
      shallowMount(Component, {
      scopedSlots: {
      foo: function(props) {
      return this.$createElement('div', props.index)
      }
      }
      })

      或者你可以使用 JSX。如果你在一个方法里撰写 JSX,babel-plugin-transform-vue-jsx 会自动注入 this.$createElement

      1
      2
      3
      4
      5
      6
      7
      shallowMount(Component, {
      scopedSlots: {
      foo(props) {
      return <div>{props.text}</div>
      }
      }
      })
    • stubs:{ [name: string]: Component | boolean } | Array

      将子组件存根。可以是一个要存根的组件名的数组或对象。如果 stubs 是一个数组,则每个存根都是一个 <${component name}-stub>

      示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      import Foo from './Foo.vue'

      mount(Component, {
      stubs: ['registered-component']
      })

      shallowMount(Component, {
      stubs: {
      // 使用一个特定的实现作为存根
      'registered-component': Foo,
      // 使用创建默认的实现作为存根。
      // 这里默认存根的组件名是 `another-component`。
      // 默认存根是 `<${the component name of default stub}-stub>`。
      'another-component': true
      }
      })
    • mocks: Object

      为实例添加额外的属性。在伪造全局注入的时候有用。

      示例:

      1
      2
      3
      4
      5
      6
      7
      const $route = { path: 'http://www.example-path.com' }
      const wrapper = shallowMount(Component, {
      mocks: {
      $route
      }
      })
      expect(wrapper.vm.$route.path).toBe($route.path)
    • localVue: Vue

      通过 [./createLocalVue.md] 创建的一个 Vue 的本地拷贝,用于挂载该组件的时候。在这份拷贝上安装插件可以防止原始的 Vue 被污染。

      示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      import { createLocalVue, mount } from '@vue/test-utils'
      import VueRouter from 'vue-router'
      import Foo from './Foo.vue'

      const localVue = createLocalVue()
      localVue.use(VueRouter)

      const routes = [{ path: '/foo', component: Foo }]

      const router = new VueRouter({
      routes
      })

      const wrapper = mount(Component, {
      localVue,
      router
      })
      expect(wrapper.vm.$route).toBeInstanceOf(Object)
    • attachToDocument: Boolean

      当设为 true 时,组件在渲染时将会挂载到 DOM 上。

      如果添加到了 DOM 上,你应该在测试的最后调用 wrapper.destroy() 将元素从文档中移除并销毁组件实例。

    • attrs: Object

      设置组件实例的 $attrs 对象。

    • propsData

      在组件被挂载时设置组件实例的 prop。

      示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      const Component = {
      template: '<div>{{ msg }}</div>',
      props: ['msg']
      }
      const wrapper = mount(Component, {
      propsData: {
      msg: 'aBC'
      }
      })
      expect(wrapper.text()).toBe('aBC')

      提示

      值得注意的是 propsData 实际上是一个 Vue API,不是 Vue Test Utils 的挂载选项。它会被 extends 处理。请查阅其它选项

    • listeners: Object

      设置组件实例的 $listeners 对象。

    • parentComponent: Object

      用来作为被挂载组件的父级组件。

      示例:

      1
      2
      3
      4
      5
      6
      import Foo from './Foo.vue'

      const wrapper = shallowMount(Component, {
      parentComponent: Foo
      })
      expect(wrapper.vm.$parent.$options.name).toBe('foo')
    • provide: Object

      为组件传递用于注入的属性。可查阅 provie/inject 了解更多。

      示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      const Component = {
      inject: ['foo'],
      template: '<div>{{this.foo()}}</div>'
      }

      const wrapper = shallowMount(Component, {
      provide: {
      foo() {
      return 'fooValue'
      }
      }
      })

      expect(wrapper.text()).toBe('fooValue')
    • 其它选项

      mountshallowMount 的选项包含了挂载选项之外的选项时,则会将它们通过扩展覆写到其组件选项。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      const Component = {
      template: '<div>{{ foo() }}{{ bar() }}{{ baz() }}</div>',
      methods: {
      foo() {
      return 'a'
      },
      bar() {
      return 'b'
      }
      }
      }
      const options = {
      methods: {
      bar() {
      return 'B'
      },
      baz() {
      return 'C'
      }
      }
      }
      const wrapper = mount(Component, options)
      expect(wrapper.text()).toBe('aBC')

ShallowMount

用法与mount相同,只不过会将所有子组件给stub,类似mount的options中的stub属性把所有子组件填写后的效果

render

将一个对象渲染成为一个字符串并返回一个 cheerio 包裹器

Cheerio 是一个类似 jQuery 的库,可以在 Node.js 中游览 DOM 对象。它的 API 和 Vue Test Utils 的 Wrapper 类似。

render 在底层使用 vue-server-renderer 将一个组件渲染为静态的 HTML。

render 被包含在了 @vue/server-test-utils 包之中。

renderToString

将一个组件渲染为 HTML。

renderToString 在底层使用 vue-server-renderer 将一个组件渲染为 HTML。

selector

很多方法的参数中都包含选择器。一个选择器可以是一个 CSS 选择器、一个 Vue 组件或是一个查找选项对象

createLocalVue

  • 返回值:
    • {Component}
  • 用法:

createLocalVue 返回一个 Vue 的类供你添加组件、混入和安装插件而不会污染全局的 Vue 类。

可通过 options.localVue 来使用:

1
2
3
4
5
6
7
8
9
10
11
12
import { createLocalVue, shallowMount } from '@vue/test-utils'
import Foo from './Foo.vue'

const localVue = createLocalVue()
const wrapper = shallowMount(Foo, {
localVue,
mocks: { foo: true }
})
expect(wrapper.vm.foo).toBe(true)

const freshWrapper = shallowMount(Foo)
expect(freshWrapper.vm.foo).toBe(false)

createWrapper(node [, options])

  • 参数:

    • {vm|HTMLElement} node

    • {Object} options
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19

      - `{Boolean} attachedToDocument`

      - **返回值:**

      - `{Wrapper}`

      - **用法:**

      `createWrapper` 为一个被挂载的 Vue 实例或一个 HTML 元素创建一个 `Wrapper`。

      ```js
      import { createWrapper } from '@vue/test-utils'
      import Foo from './Foo.vue'

      const Constructor = Vue.extend(Foo)
      const vm = new Constructor().$mount()
      const wrapper = createWrapper(vm)
      expect(wrapper.vm.foo).toBe(true)

Wrapper

上述的API都是为了创建一个vue实例的wrapper,那么创建完成之后,这个wrapper自身也是有很多接口的。

具体就在这里不重复了,大致的作用类似与options对wrapper的配置,wrapper的方法可以在wrapper实例产生之后去更改和获取某些属性,如setMethods可以改变vue中的methods,trigger可以触发某个组件上的指定事件。

这里我们就举个例子通过不同的方式来测试vue中的methods来说明它的用法:

  • 第一种就是官方文档的示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import { mount } from '@vue/test-utils'
    import sinon from 'sinon'
    import Foo from './Foo.vue'

    const wrapper = mount(Foo)
    const clickMethodStub = sinon.stub()

    wrapper.setMethods({ clickMethod: clickMethodStub })
    wrapper.find('button').trigger('click')
    expect(clickMethodStub.called).toBe(true)

    这里要注意,不同版本的setMethods不太一样,有的版本它是一个同步方法,佐伊上述写法是对的,但是有的版本是异步方法,就需要等待它生效才可以,如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import { mount } from '@vue/test-utils'
    import sinon from 'sinon'
    import Foo from './Foo.vue'
    import Vue from 'vue';

    const wrapper = mount(Foo)
    const clickMethodStub = sinon.stub()

    wrapper.setMethods({ clickMethod: clickMethodStub })
    await Vue.nextTick();
    wrapper.find('button').trigger('click')
    expect(clickMethodStub.called).toBe(true)
  • 第二种方法是在创建wrapper时传入一个options,这里利用的是mount会将options中的其他选项通过extend扩展如wrapper中的特性,具体代码可以参考options中的其他选项部分的代码。

上述两种方式都可以结合sinon去mock一个vue实例中的方法用于测试。

当然也有其他的方式可以传入一个方法进入wrapper中,如options中的context和provide等。