vue test utils 使用笔记

Vue Test Utils文档写的是真的好,第一次做笔记做的这么舒服,copycopycopy…

1、明白要测试的是什么,只关注输入输出

不推荐针对代码逐行测试,一个简单的测试用例将会断言一些输入 (用户的交互或 prop 的改变) 提供给某组件之后是否导致预期结果 (渲染结果或触发自定义事件)。

2、可以通过shallowMount渲染单个组件而不会渲染它的子组件

渲染过多组件会导致测试变慢

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

const wrapper = shallowMount(Component)
wrapper.vm // 挂载的 Vue 实例

3、组件的生命周期

beforeDestroy 和 destroyed不会主动触发,需要手动调用Wrapper.destroy()。
另外组件在每个测试规范结束时并不会被自动销毁,(setInterval 或者 setTimeout)也不会被销毁,需要手动销毁。

4、关于$emit

4.1、emit触发的事件会被wrappe记录下来,可以对事件进行断言

wrapper.vm.$emit('foo')
wrapper.vm.$emit('foo', 123)

/*
`wrapper.emitted()` 返回以下对象:
{
  foo: [[], [123]]
}
*/

4.2、可以通过访问子组件实例来触发一个自定义事件

import { mount } from '@vue/test-utils'
import ParentComponent from '@/components/ParentComponent'
import ChildComponent from '@/components/ChildComponent'

describe('ParentComponent', () => {
  it("displays 'Emitted!' when custom event is emitted", () => {
    const wrapper = mount(ParentComponent)
    wrapper.find(ChildComponent).vm.$emit('custom')
    expect(wrapper.html()).toContain('Emitted!')
  })
})

5、设置prop和data

it('manipulates state', async () => {
  await wrapper.setData({ count: 10 })
  await wrapper.setProps({ foo: 'bar' })
})
// 或者直接在mount时传入

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

mount(Component, {
  propsData: {
    aProp: 'some value'
  }
})

6、触发事件

Wrapper 暴露了一个 trigger 方法,它可以用来触发 DOM 事件,注意trigger是异步的。

test('triggers a click', async () => {
  const wrapper = mount(MyComponent)

  // 可以手动往事件中添加参数
  await wrapper.find('button').trigger('click'{ eventParams: 'xxxx' })
})

触发键盘事件,被支持的按键名参考 官方文档

 await wrapper.trigger('keydown.up')

7、任何导致操作 DOM 的改变都应该在断言之前 await nextTick 函数

测试异步代码时必须使用await或者promise.then

// 错误不会被捕获
it('will time out', done => {
  Vue.nextTick(() => {
    expect(true).toBe(false)
    done()
  })
})

// 接下来的三项测试都会如预期工作
it('will catch the error using done', done => {
  Vue.config.errorHandler = done
  Vue.nextTick(() => {
    expect(true).toBe(false)
    done()
  })
})

it('will catch the error using a promise', () => {
  return Vue.nextTick().then(function() {
    expect(true).toBe(false)
  })
})

it('will catch the error using async/await', async () => {
  await Vue.nextTick()
  expect(true).toBe(false)
})

wapper内置的一些方法内部使用Vue.nextTick()做了包装,会返回Vue.nextTick(),比如trigger等,可以被 await 的方法有:

  • setData
  • setValue
  • setChecked
  • setSelected
  • setProps
  • trigger

8、使用 Jest 运行测试并模拟 HTTP 库axios

<template>
  <button @click="fetchResults">{{ value }}</button>
</template>

<script>
  import axios from 'axios'

  export default {
    data() {
      return {
        value: null
      }
    },

    methods: {
      async fetchResults() {
        const response = await axios.get('mock/service')
        this.value = response.data
      }
    }
  }
</script>

测试代码如下:

import { shallowMount } from '@vue/test-utils'
import Foo from './Foo'
jest.mock('axios', () => ({
  get: Promise.resolve('value')
}))

it('fetches async when a button is clicked', () => {
  const wrapper = shallowMount(Foo)
  wrapper.find('button').trigger('click')
  expect(wrapper.text()).toBe('value')
})

9、配合 Vue Router 使用

可以使用mock伪造 $route 和 $router 也可以安装vue router。

但在一个 localVue 上安装 Vue Router 时也会将 $route 和 $router 作为两个只读属性添加给该 localVue。这意味着这两种方案只能二选一。

使用vue router时应该尽量避免在构造函数中安装,可以使用createLocalVue定义一个localVue,在localVue上安装。localVue 是一个独立作用域的 Vue 构造函数,我们可以对其进行改动而不会影响到全局的 Vue 构造函数。

使用localVue安装vue router

import { shallowMount, createLocalVue } from '@vue/test-utils'
import VueRouter from 'vue-router'

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

shallowMount(Component, {
  localVue,
  router
})

伪造 $route 和 $router

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

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

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

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

10、配合 Vuex 使用

核心思路是使用伪造

站在测试的角度,我们不关心这个 action 做了什么或者这个 store 是什么样子的。我们只需要知道这些 action将会在适当的时机触发,以及它们触发时的预期值。
为了完成这个测试,我们需要在浅渲染组件时给 Vue 传递一个伪造的 store。

10.1 伪造store

import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import Actions from '../../../src/components/Actions'

const localVue = createLocalVue()

localVue.use(Vuex)

describe('Actions.vue', () => {
  let actions
  let store

  beforeEach(() => {
    actions = {
      actionClick: jest.fn(),
      actionInput: jest.fn()
    }
    store = new Vuex.Store({
      state: {},
      actions
    })
  })

  it('dispatches "actionInput" when input event value is "input"', () => {
    const wrapper = shallowMount(Actions, { store, localVue })
    const input = wrapper.find('input')
    input.element.value = 'input'
    input.trigger('input')
    expect(actions.actionInput).toHaveBeenCalled()
  })

  it('does not dispatch "actionInput" when event value is not "input"', () => {
    const wrapper = shallowMount(Actions, { store, localVue })
    const input = wrapper.find('input')
    input.element.value = 'not input'
    input.trigger('input')
    expect(actions.actionInput).not.toHaveBeenCalled()
  })

  it('calls store action "actionClick" when button is clicked', () => {
    const wrapper = shallowMount(Actions, { store, localVue })
    wrapper.find('button').trigger('click')
    expect(actions.actionClick).toHaveBeenCalled()
  })
})

10.2 伪造 Getter

import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import Getters from '../../../src/components/Getters'

const localVue = createLocalVue()

localVue.use(Vuex)

describe('Getters.vue', () => {
  let getters
  let store

  beforeEach(() => {
    getters = {
      clicks: () => 2,
      inputValue: () => 'input'
    }

    store = new Vuex.Store({
      getters
    })
  })

  it('Renders "store.getters.inputValue" in first p tag', () => {
    const wrapper = shallowMount(Getters, { store, localVue })
    const p = wrapper.find('p')
    expect(p.text()).toBe(getters.inputValue())
  })

  it('Renders "store.getters.clicks" in second p tag', () => {
    const wrapper = shallowMount(Getters, { store, localVue })
    const p = wrapper.findAll('p').at(1)
    expect(p.text()).toBe(getters.clicks().toString())
  })
})

10.3 伪造 Module

import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import MyComponent from '../../../src/components/MyComponent'
import myModule from '../../../src/store/myModule'

const localVue = createLocalVue()

localVue.use(Vuex)

describe('MyComponent.vue', () => {
  let actions
  let state
  let store

  beforeEach(() => {
    state = {
      clicks: 2
    }

    actions = {
      moduleActionClick: jest.fn()
    }

    store = new Vuex.Store({
      modules: {
        myModule: {
          state,
          actions,
          getters: myModule.getters
        }
      }
    })
  })

  it('calls store action "moduleActionClick" when button is clicked', () => {
    const wrapper = shallowMount(MyComponent, { store, localVue })
    const button = wrapper.find('button')
    button.trigger('click')
    expect(actions.moduleActionClick).toHaveBeenCalled()
  })

  it('renders "state.clicks" in first p tag', () => {
    const wrapper = shallowMount(MyComponent, { store, localVue })
    const p = wrapper.find('p')
    expect(p.text()).toBe(state.clicks.toString())
  })
})

10.4 测试vuex

文档中提供了两种测试方式,一种是分别针对方法进行测试,另一种模拟真实运行环境去测试。根据优缺点对比,一般使用第一种

测试mutations

// mutations.js
export default {
  increment(state) {
    state.count++
  }
}

// mutations.spec.js

import mutations from './mutations'

test('"increment" increments "state.count" by 1', () => {
  const state = {
    count: 0
  }
  mutations.increment(state)
  expect(state.count).toBe(1)
})

测试getters

// getters.js
export default {
  evenOrOdd: state => (state.count % 2 === 0 ? 'even' : 'odd')
}

// getters.spec.js

import getters from './getters'

test('"evenOrOdd" returns even if "state.count" is even', () => {
  const state = {
    count: 2
  }
  expect(getters.evenOrOdd(state)).toBe('even')
})

test('"evenOrOdd" returns odd if "state.count" is odd', () => {
  const state = {
    count: 1
  }
  expect(getters.evenOrOdd(state)).toBe('odd')
})

11 关于测试依赖的引入

通常使用localVue去引入测试依赖,createLocalVue 返回一个 Vue 的类供你添加组件、混入和安装插件而不会污染全局的 Vue 类。

举个栗子,假如需要引入element-ui

import { createLocalVue, mount } from '@vue/test-utils';
import { Pagination } from 'element-ui';

const localVue = createLocalVue();
localVue.use(Pagination);

const wrapper = shallowMount(Foo, {
  localVue
})
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>