为vue的项目添加单元测试

3,465 阅读6分钟

动机

  • 单元测试能避免出现一些代码运行结果与预期不符的错误,通常是一些比较低级但又难以发现的问题。
  • 粗心且懒,在每次调整之后,需要不断地检查代码,反复去走流程。担心由于自己的改动而导致了逻辑上的错误。而这里面的一大部分工作其实可以让单元测试来完成。
  • 有了单元测试之后,可以对代码本身形成一种规范。如果在进行单元测试过程中发现自己的一些代码不方便进行测试,那么你可能需要重新审视这些代码,看是否有一些设计上不合理或者可以优化的地方。
  • 嵌入了单元测试的项目显得更加的专业,也会更有逼格,测试本身是开发环节需要做的内容。

工具选取对比(一个合适测试框架 -- Jest)

之前也没有去接触过前端的单元测试,也是这几天开始了解,开始并没有头绪,所以就在网上以及github上去看了一些之前比较流行的测试框架。发现比较流行的是karma + mocha + Chrome的组合。当我单独一个个去看的时候,发现其内容还是比较的多的。之后选取了jest也是经过对比权衡的

优点

  1. 一站式的解决方案,学习成本更低,上手更快(很适合现如今我的需求)

在使用 Jest 之前,我需要一个测试框架(mocha),需要一个测试运行器(karma),需要一个断言库(chai),需要一个用来做 spies/stubs/mocks 的工具(sinon 以及 sinon-chai 插件),一个用于测试的浏览器环境(可以是 Chrome 浏览器,也可以用 PhantomJS)。 而使用 Jest 后,只要安装它,全都搞定了。

  1. 全面的官方文档,易于学习和使用

Jest 的官方文档很完善,对着文档很快就能上手。而在之前,我需要学习好几个插件的用法,至少得知道 mocha 用处和原理吧 我得学会 karma 的配置和命令,chai 的各种断言方法……,经常得周旋于不同的文档站之间,其实是件很烦也很低效的事

  1. 更直观明确的测试信息提示
  2. 方便的命令行工具

缺点

jsdom 的一些局限性:因为 Jest 是基于 jsdom 的,jsdom 毕竟不是真实的浏览器环境,它在测试过程中其实并不真正的“渲染”组件。这会导致一些问题,例如,如果组件代码中有一些根据实际渲染后的属性值进行计算(比如元素的 clientWidth)就可能出问题,因为 jsdom 中这些参数通常默认是 0.


综上所述,最终我确定下来的方案是使用成熟好用的测试工具库 --- vue-test-utils 其前身是 avoriaz,avoriaz 也是一个不错的包,但其 README 中有说明,当 vue-test-utils 正式发布的时候, 它将会被废弃。 vue-test-utils 能极大地简化 Vue.js 单元测试。 例如:Vue 单元测试,一般是像下面这样的(包括 vue-cli 提供的模板里默认也是这样):

import Vue from 'vue'
import HelloWorld from '@/components/HelloWorld'

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

使用 vue-test-utils 后,你可以像下面这样

import { shallow } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld'

describe('HelloWorld.vue', () => {
  it('should render correct contents', () => {
    const wrapper = shallow(HelloWorld, {
      attachToDocument: ture
    })

    expect(wrapper.find('.hello h1').text()).to.equal('Welcome to Your Vue.js App')
  })
})

可以看到代码更加简洁了。wrapper 内含许多有用的方法,上面的例子中所使用的 find() 其中最简单不过的一个。vue-test-utils 还有 createLocalVue() 等方法以及 stub 之类的功能,基本上可以完成绝大部分情况下的测试用例,这也是非常的实用的了。

安装使用

安装使用的方式很简单,由于想引入到现有的项目中来,现有的项目大多是vue-cli创建的,所以一开始的时候基本上是已经安装并配置好了 webpack、vue-loader 和 Babel。如果是比较原始的项目,也是可以单独安装的。

  • 我们要做的第一件事就是安装 Jest 和 Vue Test Utils:
$ npm install --save-dev jest @vue/test-utils
  • 然后我们需要在 package.json 中定义一个单元测试的脚本。
// package.json
{
  "scripts": {
    "test": "jest"
  }
}
  • 在 Jest 中处理单文件组件
npm install --save-dev vue-jest
  • 接下来在 package.json 中创建一个 jest 块:
{
  // ...
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      // 告诉 Jest 处理 `*.vue` 文件
      "vue"
    ],
    "transform": {
      // 用 `vue-jest` 处理 `*.vue` 文件
      ".*\\.(vue)$": "vue-jest"
    }
  }
}

具体的使用步骤


此处我根据自己的需求来进行整理

  • 对页面内容的测试
// viewTest.vue
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <p>1212121</p>
  </div>
</template>

<script>
export default {
  name: 'viewTest',
  data () {
    return {
      msg: 'Welcome to Your Vue.js App'
    }
  }
}
// viewTest.spec
import { mount } from '@vue/test-utils'
import Component from '../../../src/components/viewTest'

describe('页面展示测试', () => {
  test('检查元素是否存在', () => {
    const wrapper = mount(Component)
    expect(wrapper.contains('.hello h1')).toBe(true)
    console.log(wrapper.find('.hello h1').text())
    expect(wrapper.text()).toContain('Welcome')
  })
})

这个是最简单的对页面的dom节点的测试,以及可以对文案进行一些测试,这些是比较基础的

  • 对事件处理的测试
// event.vue
<template>
      <div>
        <h1>My To Do event</h1>
        <h2>wawawawawawa</h2>
        <input v-model="newItem">
        <button @click="addItemToList">Add</button>
        </br>
        <!--displays event -->
        <ul>
          <li v-for="item in listItems">{{ item }}</li>
        </ul>
      </div>
    </template>

    <script>
    export default {
      name: 'event',
      data () {
        return {
          listItems: ['buy food', 'play games', 'sleep'],
          newItem: ''
        }
      },
      methods: {
        addItemToList() {
          this.listItems.push(this.newItem);
          this.newItem = '';
        }
      }
    }
    </script>
// event.spec.js
// 从测试实用工具集中导入 `mount()` 方法
// 同时导入你要测试的组件
import { mount } from '@vue/test-utils'
import Component from '../../../src/components/itemEvent'

describe('事件触发测试', () => {
  test('事件触发测试', () => {
  // 现在挂载组件,你便得到了这个包裹器
    const wrapper = mount(Component)
    const button = wrapper.find('button')
    wrapper.setData({
      newItem: '添加测试项',
    })
    button.trigger('click')
    console.log(wrapper.text())
    expect(wrapper.text()).toContain('添加测试项')
  })
})

这里是在模拟用户交互的一个测试,当用户点击按钮的时候会把数据插入到当前的列表中来,所以最开始需要定位到这个按钮,可以用find(),之后要去触发这个事件, button.trigger('click'),然后把预期的结果,与按照流程的结果相比较,以达到测试的效果。这里模拟的是一个点击事件,当然,api也支持各种的鼠标事件以及键盘事件。

  • 测试异步行为 平时的业务场景中肯定是离不开异步操作的,当发送一个接口请求的时候应该怎么去才做。Jest 运行测试用例同时可以模拟了 HTTP 库 axios,对预期结果可以进行设定和比较,比如:
// axios.js
export default {
  get: () => Promise.resolve({ data: 'response' })
}
<template>
  <div>
    <button @click="fetchResults">发送请求</button>
    {{value}}
  </div>

</template>

<script>
import axios from '../axios.js'

export default {
  data () {
    return {
      value: '初始值'
    }
  },
  methods: {
    async fetchResults () {
      const response = await axios.get('mock/service')
      this.value = response.data
      console.log(this.value)
    }
  }, 
  created (){
    console.log(axios.get)
  }
}
</script>
// async.spec.js
import { shallowMount } from '@vue/test-utils'
import async from '../../../src/components/async'
jest.mock('axios')

it('当点击按钮发送请求时检验返回值', () => {
  const wrapper = shallowMount(async)
  console.log(jest)
  wrapper.find('button').trigger('click')
  // expect(wrapper.value)
  expect(wrapper.vm.value).toBe('response')
  // console.log(wrapper.vm.value).toBe('初始值')
})

这个时候运行的话会报错误

因为断言在 fetchResults 中的 Promise 完成之前就被调用了,所以value的值还是最开始的初始值。大多数单元测试库都提供一个回调来使得运行期知道测试用例的完成时机。Jest 和 Mocha 都是用了 done。我们可以和 $nextTick 或 setTimeout 结合使用 done 来确保任何 Promise 都会在断言之前完成。

  • 测试 Vue Router 使用