為vue的專案新增單元測試

林暉發表於2018-12-18

動機

  • 單元測試能避免出現一些程式碼執行結果與預期不符的錯誤,通常是一些比較低階但又難以發現的問題。
  • 粗心且懶,在每次調整之後,需要不斷地檢查程式碼,反覆去走流程。擔心由於自己的改動而導致了邏輯上的錯誤。而這裡面的一大部分工作其實可以讓單元測試來完成。
  • 有了單元測試之後,可以對程式碼本身形成一種規範。如果在進行單元測試過程中發現自己的一些程式碼不方便進行測試,那麼你可能需要重新審視這些程式碼,看是否有一些設計上不合理或者可以優化的地方。
  • 嵌入了單元測試的專案顯得更加的專業,也會更有逼格,測試本身是開發環節需要做的內容。

工具選取對比(一個合適測試框架 -- 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('初始值')
})
複製程式碼

這個時候執行的話會報錯誤

為vue的專案新增單元測試
因為斷言在 fetchResults 中的 Promise 完成之前就被呼叫了,所以value的值還是最開始的初始值。大多數單元測試庫都提供一個回撥來使得執行期知道測試用例的完成時機。Jest 和 Mocha 都是用了 done。我們可以和 $nextTick 或 setTimeout 結合使用 done 來確保任何 Promise 都會在斷言之前完成。

  • 測試 Vue Router 使用

相關文章