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
})
1321

被折叠的 条评论
为什么被折叠?



