vue單元測試vue test utils使用初探

genetalks_大資料發表於2019-04-04

簡介

最近在做一個專案的重構,技術選型為vue-cli 3.0 + typescript + vue-router + sass.因為我負責的模組比較少比較簡單,所以老大讓我先把負責部分的測試程式碼寫好。至此我才第一次接觸到測試程式碼,我們專案使用的測試工具是jest,與vue官方出的單元測試工具庫vue-test-utils配合使用。第一次接觸測試程式碼,開始的時候還是一臉懵逼,有種學習一門新語言的趕腳。經過幾天的摸索之後學會了簡單的編寫測試程式碼,並對幾種情況進行特殊處理。本文是一篇vue單元測試的基礎入門文章,只介紹測試程式碼,需要了解搭建測試框架的朋友可以自行參閱vue-test-utils官方文件等資料。

1.什麼是測試程式碼

簡言之,測試程式碼就是通過程式碼模擬一個vue元件的執行環境,使被測試的元件在這個環境下執行看是否能夠得到期望的執行結果。如果執行結果與期望的結果相同,則說明該測試用例通過。下面我們來看一個最簡單的例項:

  • 一個簡單的vue元件的測試
// message.vue
<template>
  <div>
    <p>{{message}}</p>
  </div>
<template>
<script>
  export default{
    data(){
      return{
        message: 'a test component',
      }
    }
  }
</script>
複製程式碼

上面這個最簡單的vue元件,就是將data中的message值渲染到p標籤中去。對與這樣的元件,測試程式碼如下

import { mount } from '@vue/test-utils';
import message from './message.vue';
describe('測試message.vue元件的測試套件,可含有多個測試用例', () =>{
  it('這是測試message元件p標籤能否正常渲染文字的一個測試測試用例', () => {
    const wrapper = mount(message, {}) // 使用mount可以建立一個包涵被掛載和渲染的一個例項
    expect(wrapper.find('p').text()).toBe('a test component') // expect是jest中的斷言,即判斷該語句前後是否相等
  })
})
複製程式碼

上面這句expect().toBe()斷言,用於判斷我們用message.vue元件生成的例項中,p 標籤中的文字是否等於元件 data 中的 message 的值'a test component',如果相等,則說明此斷言為真。

  • 一個稍複雜元件的測試
// count.vue
<template>
  <div>
    <p>{{count}}</p>
    <button @click="add">增加</button>
  </div>
</template>
<script>
export default {
  data() {
    return{
      count: 0
    }
  },
  methods: {
    add() {
      this.count++
    }
  },
}
</script>
複製程式碼

上面是一個帶有簡單互動的vue元件,count初始值為0,使用者點選一次增加按鈕,p標籤中的值即加一。對於這樣的元件我們的測試程式碼為

import { mount } from '@vue/test-utils';
import count from 'count.vue';
describe('count.vue元件', () => {
  it('測試count元件能否正常顯示並增加', () => {
    const wrapper = mount(count, {}) // 使用 mount 建立一個vue元件例項 wrapper
    expect(wrapper.find('p').text()).toBe(0); // 判斷p標籤中的值是否為初始化0
    wrapper.find('button').trigger('click'); // 使用trigger('click')模擬使用者的點選操作
    expect(wrapper.find('p').text()).toBe(1); // 經過模擬點選操作後,count的值應該增加成為1
  })
})
複製程式碼

通過上面的例子我們看到,測試程式碼可以模擬出使用者的操作,通過對操作之後的結果進行斷言,能判斷出該元件能否通過測試。而且一個測試用例中可以有多個斷言,只有全部斷言通過才說明該元件例項通過測試。只要有一個斷言未通過,則說明該元件例項未通過測試。

2.測試帶有回撥的非同步請求

在專案中我們很常見到vue元件內向介面請求資料的情況,那麼我們如何測試這種非同步請求呢。官方也給了我們例項程式碼:

// axios模擬
export default{
  get: () => Promise.resolve({ date: 'value' })
}
複製程式碼
// getValue.vue
<template>
  <button @click="fetchResults" />
</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>
複製程式碼
// getValue.test.js
import { shallowMount } from '@vue/test-utils'
import Foo from './Foo'
jest.mock('axios') // jest 模擬axios庫

describe('getValue.vue元件', () => {
  it('點選button時,非同步獲取介面返回的value值', done => {
    const wrapper = shallowMount(Foo)
    wrapper.find('button').trigger('click')
    wrapper.vm.$nextTick(() => { // 使用$nextTick 在Promise執行後再進行斷言
      expect(wrapper.vm.value).toBe('value') // 對非同步獲取的資料進行斷言,判斷獲取的值與期望是否都相等
      done() // 使用done()結束回撥
    })
  })
})
複製程式碼

上面的這個官方demo是通過呼叫元件內的介面,並對介面返回的資料進行斷言。一開始我也照著官方給的程式碼使用,但是很快發現了一些隨之出現的問題。

1. 呼叫元件本身的介面,意味著需要在元件內部傳入介面請求時所需要的引數,這個資料我們可以自己傳入建立的vue例項中,但是如果對於全部介面都自己傳入資料會非常麻煩。而且還分成傳入的引數正確或錯誤等不同情況。
2. 呼叫vue元件本身的http請求測試,必須得有後臺介面配合,不能獨立出來測試。而且測試會對後臺產生真實的請求記錄,因為我們開發過程中不知道會測試多少次,所以會給後臺新增很多無用的請求記錄。
3. 因為公司開發專案時,使用的是docker建立的虛擬容器作為執行環境,配置了網路地址轉發,這使得我vue元件中的請求無法成功,也是這條因素使得我不能使用官方給的測試方法。

然後我就請教了我們的老大,在他的幫助下我使用另一種方法測試非同步請求,做法是攔截元件中的非同步請求,使用自己模擬的http請求。程式碼如下:

// 需要改造一下我們的 axios 請求
import axios from 'axios'
export getValue(...arg){
  return axios.get('mock/service',..arg).then(res=>{
    Promise.resolve({ date: 'value' })
  })
}
複製程式碼
// getValue.vue
<template>
  <button @click="fetchResults" />
</template>
<script>
  import getValue from 'axios'
  export default {
    data() {
      return {
        value: null
      }
    },
    methods: {
      fetchResults() {
        getValue(...arg).then(res=>{
          this.value = res.date
        })
      }
    }
  }
</script>
複製程式碼
// getValue.test.js
import { shallowMount } from '@vue/test-utils'
import * as svc from 'axios'
import Foo from './Foo'

describe('getValue.vue元件', () => {
  it('點選button時,非同步獲取介面返回的value值', done => {
    const getValue = jest.spyOn(svc, 'getValue') // 使用jest.spyOn()建立一個mock函式
    getValue.mockReturnValueOnce(Promise.resolve({data: 'value')) // 模擬我們自己mock函式的返回值
    const wrapper = shallowMount(Foo)
    wrapper.find('button').trigger('click') // 模擬使用者點選事件
    wrapper.vm.$nextTick(() => { // 使用$nextTick 在Promise執行後再進行斷言
      expect(getValue).toBeCalled(); // 斷言是否請求了自己mock的getValue函式
      expect(wrapper.vm.value).toBe('value') // 對非同步獲取的資料進行斷言,判斷獲取的值與期望是否都相等
      done() // 使用done()結束回撥
    })
  })
})
複製程式碼

jest.spyOn()方法建立一個mock函式,這個mock的函式會在元件的介面請求的時候被執行,並返回我們給mock函式新增的返回值,通過判斷這個mock函式是否被執行,以及元件獲取的返回值與我們給mock函式新增的返回值是否相等就可以判斷元件的非同步請求是否能夠正確執行。通過這種方式,我們來測試非同步元件。

實際上我的同事,之前也寫過一篇在react專案中使用jest測試的文章,其中也介紹了使用jest.spyOn()來測試非同步請求的情況。感興趣的話可以去這裡結合瞭解一下。

3. 測試在有路由情況下的vue元件

vue 官方也給出了vue-test-util 配合 vue-router 使用的文件。我工作中出現的一個情況是要測試在某個路由地址下,<router-view> 載入的子元件的測試。但是到目前為止本人還沒有按照官方的例項跑通過測試,很尷尬,也許是我開啟的姿勢不對,等之後正確實現之後會把方法再補上。下面我介紹一個對這種情況測試的非官方的寫法。

import Vue from 'vue'
import VueRouter from 'vue-router'
import totest from 'src/components/totest'

describe('totest.vue', () => {
  it('should totest renders stuff', (done) => {
    Vue.use(VueRouter)
    const router = new VueRouter({routes: [ // 定義路由,其中使用了被測試元件
      {path: '/totest/:id', name: 'totest', component: totest},
      {path: '/wherever', name: 'another_component', component: {render: h => '-'}},
    ]})
    const vm = new Vue({ // 自己新建一個帶 router 且有 router-view 的vue例項
      el: document.createElement('div'),
      router: router,
      render: h => h('router-view')
    })
    router.push({name: 'totest', params: {id: 123}}) // 使用測試元件的路由
    Vue.nextTick(() => {
      console.log('html:', vm.$el)
      expect(vm.$el.querySelector('h2').textContent).to.equal('Fred Bloggs');
      done()
    })
  })
})
複製程式碼

上面這個測試程式碼很巧妙的新建了一個使用router的vue例項,然後把被測試元件加到路由中去,當改變路由地址時,被測試元件就會被執行。此時可以對被測試元件進行斷言。

4. 其他問題

在開發過程中還遇到了一些其他問題,如:

1. vue專案使用了element元件,測試程式碼報錯,類似於 el-button 未註冊
2. 專案使用了vue-i18n做國際化,測試程式碼報錯,報錯資訊為 vm._$t is not a function...
3. 使用國際化vue-i18n的時候,vue元件程式碼裡如果有 i18n.locale的話測試程式碼會提示無法找到locale屬性

..........

上面我列出來的這三個問題,並沒有給出具體的解決辦法。12解決比較簡單,3目前還不知道如何處理...之後請教一下我們的大佬或者自己查閱下資料,等解決之後再來更新文章。因為每個人的專案不一樣,可能我遇到的問題別人並不會遇到,所以還是對著報錯自己查照除錯吧。這也能更加完善你自己的專案程式碼。本人也是小白,第一次學習寫測試程式碼,之後有了更深入的瞭解之後會更新這篇文章。

參考文章

vue-test-utils官方

一篇文章學會 Vue 專案單元測試

原文連結:tech.gtxlab.com/vue-test-ut…


作者簡介: 宮晨光,人和未來大資料前端工程師。

相關文章