Vue單元測試(Mocha/Karma + Vue-Test-Utils + Chai)

在使用vue-cli創建項目的時候,會提示要不要安裝單元測試和e2e測試。這篇文章我將通過一個Vue的項目, 去講解如何使用mocha & karma, 且結合vue官方推薦的vue-test-utils去進行單元測試的實戰.

簡介

Karma

Karma是一個基於Node.js的JavaScript測試執行過程管理工具(Test Runner)。該工具在Vue中的主要作用是將項目運行在各種主流Web瀏覽器進行測試。
換句話說,它是一個測試工具,能讓你的代碼在瀏覽器環境下測試。需要它的原因在於,你的代碼可能是設計在瀏覽器端執行的,在node環境下測試可能有些bug暴露不出來;另外,瀏覽器有兼容問題,karma提供了手段讓你的代碼自動在多個瀏覽器(chrome,firefox,ie等)環境下運行。如果你的代碼只會運行在node端,那麼你不需要用karma。

Mocha

Mocha是一個測試框架,在vue-cli中配合chai斷言庫實現單元測試。
Mocha的常用命令和用法不算太多,看阮一峯老師的測試框架 Mocha 實例教程就可以大致瞭解了。
而Chai斷言庫可以看Chai.js斷言庫API中文文檔,很簡單,多查多用就能很快掌握。

npm run unit 執行過程

  1. 執行 npm run unit 命令
  2. 開啓Karma運行環境
  3. 使用Mocha去逐個測試用Chai斷言寫的測試用例
  4. 在終端顯示測試結果
  5. 如果測試成功,karma-coverage 會在 ./test/unit/coverage 文件夾中生成測試覆蓋率結果的網頁。

一. 安裝

我爲本教程寫一個示例庫, 您可以直接跳過所有安裝過程, 安裝依賴後運行該示例項目:
如果想一步步進行安裝, 也可以跟着下面的步驟進行操作:

(一) 使用腳手架初始化vue項目(使用webpack模板)

//命令行中輸入(默認閱讀該文章的讀者已經安裝vue-cli和node環境)
vue init webpack vueunittest

注意, 當詢問到這一步Pick a test runner(Use arrow keys)時, 請選擇使用Karma and Mocha
在這裏插入圖片描述
下面是用 npm install -g vue-cli 構建項目時可以直接引入測試模塊,如下:
在這裏插入圖片描述
項目構建之後會有這麼一個test模塊,結構如下:
在這裏插入圖片描述
接下來的操作進入項目npm install安裝相關依賴後(該步驟可能更會出現PhantomJS這個瀏覽器安裝失敗的報錯, 不用理會, 因爲 之後我們不使用這個瀏覽器), npm run build即可.

(二) 安裝Karma-chrome-launch

接下來安裝karma-chrome-launcher, 在命令行中輸入

npm install karma-chrome-launcher --save-dev

對於Karma,我只是瞭解了一下它的配置選項
下面是Vue的karma配置,簡單註釋了下:
然後在項目中找到test/unit/karma.conf.js文件, 將PhantomJS瀏覽器修改爲Chrome不要問我爲什麼不使用PhantomJS, 因爲經常莫名的錯誤, 改成Chrome就不會!!!)

//karma.conf.js

'use strict'

const path = require('path')
const merge = require('webpack-merge')
const webpack = require('webpack')

const baseConfig = require('../../.electron-vue/webpack.renderer.config')
const projectRoot = path.resolve(__dirname, '../../src/renderer')

// Set BABEL_ENV to use proper preset config
process.env.BABEL_ENV = 'test'

let webpackConfig = merge(baseConfig, {
    devtool: '#inline-source-map',
    plugins: [
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': '"testing"'
        })
    ]
})

// don't treat dependencies as externals
delete webpackConfig.entry
delete webpackConfig.externals
delete webpackConfig.output.libraryTarget

// apply vue option to apply isparta-loader on js
webpackConfig.module.rules
    .find(rule => rule.use.loader === 'vue-loader').use.options.loaders.js = 'babel-loader'

module.exports = config => {
    config.set({
        // 瀏覽器
    	browsers: ['PhantomJS'],
 	    browsers: ['Chrome'],
    	// electron
        browsers: ['visibleElectron'],
        client: {
            useIframe: false
        },
        // 測試覆蓋率報告
        // https://github.com/karma-runner/karma-coverage/blob/master/docs/configuration.md
        coverageReporter: {
            dir: './coverage',
            reporters: [
                { type: 'lcov', subdir: '.' },
                { type: 'text-summary' }
            ]
        },
        customLaunchers: {
            'visibleElectron': {
                base: 'Electron',
                flags: ['--show']
            }
        },
        // 測試框架
        frameworks: ['mocha', 'chai'],
        // 測試入口文件
        files: ['./index.js'],
        // 預處理器 karma-webpack
        preprocessors: {
            './index.js': ['webpack', 'sourcemap']
        },
        // 測試報告
        reporters: ['spec', 'coverage'],
        singleRun: true,
        // Webpack配置
        webpack: webpackConfig,
        // Webpack中間件
        webpackMiddleware: {
            noInfo: true
        }
    })
}

Mocha和chai

我們看下官方的例子(都用註釋來解釋代碼意思了):

import Vue from 'vue' // 導入Vue用於生成Vue實例
import Hello from '@/components/Hello' // 導入組件
// 測試腳本里面應該包括一個或多個describe塊,稱爲測試套件(test suite)
describe('Hello.vue', () => {
  // 每個describe塊應該包括一個或多個it塊,稱爲測試用例(test case)
  it('should render correct contents', () => {
    const Constructor = Vue.extend(Hello) // 獲得Hello組件實例
    const vm = new Constructor().$mount() // 將組件掛在到DOM上
    //斷言:DOM中class爲hello的元素中的h1元素的文本內容爲Welcome to Your Vue.js App
    expect(vm.$el.querySelector('.hello h1').textContent)
      .to.equal('Welcome to Your Vue.js App')  
  })
})

(三) 安裝Vue-test-utils

安裝Vue.js 官方的單元測試實用工具庫, 在命令行輸入:
npm install --save-dev vue-test-utils

(四) 執行npm run unit

當你完成以上兩步的時候, 你就可以在命令行執行npm run unit嚐鮮你的第一次單元測試了, Vue腳手架已經初始化了一個HelloWorld.spec.js的測試文件去測試HelloWrold.vue, 你可以在test/unit/specs/HelloWorld.spec.js下找到這個測試文件.(提示: 將來所有的測試文件, 都將放specs這個目錄下, 並以測試腳本名.spec.js結尾命名!)
在命令行輸入npm run unit, 當你看到下圖所示的一篇綠的時候, 說明你的單元測試通過了!
在這裏插入圖片描述
第一次單元測試測試通過

需要知道的知識點:
  • 測試腳本都要放在test/unit/specs/ 目錄下。
  • 腳本命名方式爲[組件名].spec.js
  • 所謂斷言,就是對組件做一些操作,並預言產生的結果。如果測試結果與斷言相同則測試通過。
  • 單元測試默認測試 src 目錄下除了 main.js之外的所有文件,可在test/unit/index.js文件中修改。
  • Chai斷言庫中,to be been is that which and has have with at of same這些語言鏈是沒有意義的,只是便於理解而已。
  • 測試腳本由多個 descibe 組成,每個 describe 由多個it 組成。

二. 測試工具的使用方法

下面是一個Counter.vue文件, 我將以該文件爲基礎講解項目中測試工具的使用方法.

//Counter.vue

<template>
  <div>
    <h3>Counter.vue</h3>
    {{ count }}
    <button @click="increment">自增</button>
  </div>
</template>

<script>
  export default {
    data () {
      return {
        count: 0
      }
    },

    methods: {
      increment () {
        this.count++
      }
    }
  }
</script>

(一) Mocha框架

1. Mocha測試腳本的寫法

Mocha的作用是運行測試腳本, 要對上面Counter.vue進行測試, 我們就要寫測試腳本, 通常測試腳本應該與Vue組件名相同, 後綴爲spec.js. 比如, Counter.vue組件的測試腳本名字就應該爲Counter.spec.js

//Counter.spec.js

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

describe('Counter.vue', () => {

  it('點擊按鈕後, count的值應該爲1', () => {
    //獲取組件實例
    const Constructor = Vue.extend(Counter);
    //掛載組件
    const vm = new Constructor().$mount();
    //獲取button
    const button = vm.$el.querySelector('button');
    //新建點擊事件
    const clickEvent = new window.Event('click');
    //觸發點擊事件
    button.dispatchEvent(clickEvent);
    //監聽點擊事件
    vm._watcher.run();
    // 斷言:count的值應該是數字1
    expect(Number(vm.$el.querySelector('.num').textContent)).to.equal(1);
  })
})

上面這段代碼就是一個測試腳本.測試腳本應該包含一個或多個describe, 每個describe塊應該包括一個或多個it
describe塊稱爲"測試套件"(test suite), 表示一組相關的測試. 它是一個函數, 第一個參數是測試套件的名稱(通常寫測試組件的名稱, 這裏即爲Counter.js), 第二個參數是一個實際執行的函數.
it塊稱爲"測試用例"(test case), 表示一個單獨的測試, 是測試的最小單位. 它也是一個函數, 第一個參數是測試用例的名稱(通常描述你的斷言結果, 這裏即爲"點擊按鈕後, count的值應該爲1"), 第二個參數是一個實際執行的函數.

2. Mocha進行異步測試

我們在Counter.vue組件中添加一個按鈕, 並添加一個異步自增的方法爲incrementByAsync, 該函數設置一個延時器, 1000ms後count自增1.

  <template>
    ...
    <button @click="increment">自增</button>
    <button @click="incrementByAsync">異步自增</button>
    ...
  <template>
  
  <script>
     ...
     methods: {
      ...
      incrementByAsync () {
        window.setTimeout(() => {
          this.count++;
        }, 1000) 
      }
    }
  </script>

給測試腳本中新增一個測試用例, 也就是it()

  it('count異步更新, count的值應該爲1', (done) => {
    ///獲取組件實例
    const Constructor = Vue.extend(Counter);
    //掛載組件
    const vm = new Constructor().$mount();
    //獲取button
    const button = vm.$el.querySelectorAll('button')[1];
    //新建點擊事件
    const clickEvent = new window.Event('click');

    //觸發點擊事件
    button.dispatchEvent(clickEvent);
    //監聽點擊事件
    vm._watcher.run();
    //1s後進行斷言
    window.setTimeout(() => {
      // 斷言:count的值應該是數字1
      expect(Number(vm.$el.querySelector('.num').textContent)).to.equal(1);
      done();
    }, 1000);
  })

Mocha中的異步測試, 需要給it()內函數的參數中添加一個done, 並在異步執行完後必須調用done(), 如果不調用done(), 那麼Mocha會在2000ms後報錯且本次單元測試測試失敗(mocha默認的異步測試超時上線爲2000ms), 錯誤信息如下:
未調用done()的報錯
未調用done()的報錯

3. Mocha的測試鉤子

如果大家對於vue的mounted(), created()鉤子能夠理解的話, 對Mocha的鉤子也很容易理解, Mocha在describe塊中提供了四個鉤子: before(), after(), beforeEach(), afterEach(). 它們會在以下時間執行

describe('鉤子說明', function() {

  before(function() {
    // 在本區塊的所有測試用例之前執行
  });

  after(function() {
    // 在本區塊的所有測試用例之後執行
  });

  beforeEach(function() {
    // 在本區塊的每個測試用例之前執行
  });

  afterEach(function() {
    // 在本區塊的每個測試用例之後執行
  });

});

上述就是Mocha的基本使用介紹, 如果想了解Mocha的更多使用方法, 可以查看下面的文檔和一篇阮一峯的Mocha教程:

Mocha官方文檔 : https://mochajs.org/

Mocha官方文檔翻譯 : http://www.jianshu.com/p/9c78548caffa

阮一峯 - 測試框架 Mocha 實例教程 : http://www.ruanyifeng.com/blog/2015/12/a-mocha-tutorial-of-examples.html

(二) Chai斷言庫

上面的測試用例中, 以expect()方法開頭的就是斷言.

expect(Number(vm.$el.querySelector('.num').textContent)).to.equal(1);

所謂斷言, 就是判斷源碼的實際執行結果與預期結果是否一致, 如果不一致, 就會拋出錯誤. 上面的斷言的意思是指: 有.num這類名的節點的內容應該爲數字1. 斷言庫庫有很多種, Mocha並不限制你需要使用哪一種斷言庫, Vue的腳手架提供的斷言庫是sino-chai, 是一個基於Chai的斷言庫, 並且我們指定使用的是它的expect斷言風格.
expect斷言風格的優點很接近於自然語言, 下面是一些例子

  // 相等或不相等
  expect(1 + 1).to.be.equal(2);
  expect(1 + 1).to.be.not.equal(3);

  // 布爾值爲true
  expect('hello').to.be.ok;
  expect(false).to.not.be.ok;

  // typeof
  expect('test').to.be.a('string');
  expect({ foo: 'bar' }).to.be.an('object');
  expect(foo).to.be.an.instanceof(Foo);

  // include
  expect([1,2,3]).to.include(2);
  expect('foobar').to.contain('foo');
  expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');

  // empty
  expect([]).to.be.empty;
  expect('').to.be.empty;
  expect({}).to.be.empty;
  
  // match
  expect('foobar').to.match(/^foo/);

每一個it()所包裹的測試用例都應該有一句或多句斷言,上面只是介紹了一部分的斷言語法, 如果想要知道更多Chai的斷言語法, 請查看以下的官方文檔.

Chai官方文檔: http://chaijs.com/

Chai官方文檔翻譯: http://www.jianshu.com/p/f200a75a15d2

(三) Vue-test-utils測試庫

1. 在測試腳本中引入vue-test-utils
//Counter.spec.js

import Vue from 'vue'
import Counter from '@/components/Counter'
//引入vue-test-utils
import {mount} from 'vue-test-utils'
2. 測試文本內容

下面我將在Counter.spec.js測試腳本中對Counter.vue中

的文本內容進行測試, 大家可以直觀的感受一下使用了Vue-test-utils後對.vue單文件組件的測試變得多麼簡單.

未使用vue-test-utils的測試用例:

  it('未使用Vue-test-utils: 正確渲染h3的文字爲Counter.vue', () => {
    const Constructor = Vue.extend(Counter);
    const vm = new Constructor().$mount();
    const H3 = vm.$el.querySelector('h3').textContent;
    expect(H3).to.equal('Counter.vue');
  })

使用了vue-test-utils的測試用例:

  it('使用Vue-test-Utils: 正確渲染h3的文字爲Counter.vue', () => {
    const wrapper = mount(Counter);
    expect(wrapper.find('h3').text()).to.equal('Counter.vue');
  })

從上面的代碼可以看出, vue-test-utils工具將該測試用例的代碼量減少了一半, 如果是更復雜的測試用例, 那麼代碼量的減少將更爲突出. 它可以讓我們更專注於去寫文件的測試邏輯, 將獲取組件實例和掛載的繁瑣的操作交由vue-test-utils去完成.

3. vue-test-utils的常用API
  • find(): 返回匹配選擇器的第一個DOM節點或Vue組件的wrapper, 可以使用任何有效的選擇器
  • text(): 返回wrapper的文本內容
  • html(): 返回wrapper DOM的HTML字符串
  it('find()/text()/html()方法', () => {
    const wrapper = mount(Counter);
    const h3 = wrapper.find('h3');
    expect(h3.text()).to.equal('Counter.vue');
    expect(h3.html()).to.equal('<h3>Counter.vue</h3>');
  })
  • trigger(): 在該 wrapper DOM 節點上觸發一個事件。
it('trigger()方法', () => {
    const wrapper = mount(Counter);
    const buttonOfSync = wrapper.find('.sync-button');
    buttonOfSync.trigger('click');
    buttonOfSync.trigger('click');
    const count = Number(wrapper.find('.num').text());
    expect(count).to.equal(2);
  })
  • setData(): 設置data的屬性並強制更新
  it('setData()方法',() => {
    const wrapper = mount(Counter);
    wrapper.setData({foo: 'bar'});
    expect(wrapper.vm.foo).to.equal('bar');
  })

上面介紹了幾個vue-test-utils提供的方法, 如果想深入學習vue-test-utils, 請閱讀下面的官方文檔:

vue-test-utils官方文檔: https://vue-test-utils.vuejs.org/zh-cn/

三. 項目說明

該項目模仿了一個簡單的微博, 在代碼倉庫下載後, 可直接通過npm run dev運行.

(一) 項目效果圖

項目展示
項目展示

(二) 項目中的交互邏輯和需求

在文本框中輸入內容後點擊"發佈"按鈕(1), 會新發布內容到微博列表中, 且個人頭像等下的微博數量(6)會增加1個
當文本框中無內容時, 不能發佈空微博到微博列表, 且彈出提示框, 叫用戶輸入內容
當點擊"關注"(2), 個人頭像下關注的數量(5)會增加1個, 且按鈕內字體變成"取消關注"; 當點擊"取消關注"(2), 個人頭像下的數量(5)會減少1個, 且按鈕內字體變成"關注"
當點擊"收藏"(3)時, 我的收藏(7)會增加1個數量, 且按鈕內文字變成"已收藏"; 點擊"已收藏"(3)時, 我的收藏(7)會減少1個數量, 且按鈕內文字變成"收藏"
當點擊"贊"(4), 我的贊(8)會增加1個數量, 且按鈕內文字變成"取消贊"; 點擊"取消贊"(3)時, 我的贊(8)會減少1個數量, 且按鈕內文字變成"贊"

(三) 項目源碼

//SinaWeibo.vue

<template>
  <div class="weibo-page">
    <nav>
      <span class="weibo-logo"></span>
      <div class="search-wrapper">
        <input type="text" placeholder="大家正在搜: 李棠輝的文章好贊!">
        <img v-if="!iconActive" @mouseover="mouseOverToIcon" src="../../static/image/search.png" alt="搜索icon">
        <img v-if="iconActive" @mouseout="mouseOutToIcon" src="../../static/image/search-active.png" alt="搜索icon">
      </div>
    </nav>
    <div class="main-container">
      <aside class="aside-nav">
        <ul>
          <li :class="{ active: isActives[indexOfContent] }" v-for="(content, indexOfContent) in asideTab" :key="indexOfContent" @click="tabChange(indexOfContent)">
            <span>{{content}}</span>
            <span class="count">
              <span v-if="indexOfContent === 1">({{collectNum}})</span>
              <span v-if="indexOfContent === 2">({{likeNum}})</span>              
            </span>
          </li>
        </ul>
      </aside>
      <main class="weibo-content">
        <div class="weibo-publish-wrapper">
          <img src="../../static/image/tell-people.png"></img>
          <textarea v-model="newWeiboContent.content"></textarea>
          <button @click="publishNewWeiboContent">發佈</button>
        </div>
        <div 
          class="weibo-news" 
          v-for="(news, indexOfNews) in weiboNews"
          :key="indexOfNews">
          <div class="content-wrapper">
            <div class="news-title">
            <div class="news-title__left">
              <img :src="news.imgUrl">
              <div class="title-text">
                <div class="title-name">{{news.name}}</div>
                <div class="title-time">{{news.resource}}</div>
              </div>
            </div>
            <button 
              class="news-title__right add" 
              v-if="news.attention === false"
              @click="attention(indexOfNews)">
              <i class="fa fa-plus"></i>
              關注
            </button>
             <button 
              class="news-title__right cancel" 
              v-if="news.attention === true"
              @click="unAttention(indexOfNews)">
              <i class="fa fa-close"></i>
              取消關注
            </button>
          </div>
          <div class="news-content">{{news.content}}</div>
          <div class="news-image" v-if="news.images.length">
            <img 
            v-for="(img, indexOfImg) in news.images"
            :key="indexOfImg"
            :src="img">
          </div>
          </div>
          <ul class="news-panel">
            <li @click="handleCollect(indexOfNews)">
              <i class="fa fa-star-o" :class="{collected: news.collect }"></i>
              {{news.collect ? "已收藏" : '收藏'}}
            </li>
            <li>
              <i class="fa fa-external-link"></i>
              轉發
            </li>
            <li>
              <i class="fa fa-commenting-o"></i>
              評論
            </li>
            <li @click="handleLike(indexOfNews)">
              <i class="fa fa-thumbs-o-up" :class="{liked: news.like}"></i>
              {{news.like ? '取消贊' : '贊'}}
            </li>
          </ul>
        </div>

      </main>
      <aside class="aside-right">
        <div class="profile-wrapper">
          <div class="profile-top">
            <img src="../../static/image/profile.jpg">
          </div>
          <div class="profile-bottom">
            <div class="profile-name">Lee_tanghui</div>
            <ul class="profile-info">
              <li 
                v-for="(profile, indexOfProfile) in profileData"
                :key="indexOfProfile">
                <div class="number">{{profile.num}}</div>
                <div class="text">{{profile.text}}</div>
              </li>
            </ul>
          </div>
        </div>
      </aside>
    </div>
    <footer>
       Wish you like my blog! --- LITANGHUI
    </footer>
  </div>
</template>

<script>
  //引入假數據
  import * as mockData from '../mock-data.js'

  export default {
    mounted() {
      //模擬獲取數據
      this.profileData = mockData.profileData;
      this.weiboNews = mockData.weiboNews;
      this.collectNum = mockData.collectNum;
      this.likeNum = mockData.likeNum;

    },
    data() {
      return {
        iconActive: false,
        asideTab: ["首頁", "我的收藏", "我的贊"],
        isActives: [true, false, false],
        profileData: [],
        weiboNews: [],
        collectNum: 0,
        likeNum: 0,
        newWeiboContent:   {
          imgUrl: '../../static/image/profile.jpg',
          name: 'Lee_tanghui',
          resource: '剛剛 來自 網頁版微博',
          content: '',
          images: []
        },
      }
    },
    methods: {
      mouseOverToIcon() {
        this.iconActive = true;
      },
      mouseOutToIcon() {
        this.iconActive = false;
      },
      tabChange(indexOfContent) {
        this.isActives.forEach((item, index) => {
          index === indexOfContent ?
            this.$set(this.isActives, index, true) :
            this.$set(this.isActives, index, false);
        })
      },
      publishNewWeiboContent() {
        if(!this.newWeiboContent.content) {
          alert('請輸入內容!')
          return;
        }
        const newWeibo = JSON.parse(JSON.stringify(this.newWeiboContent));
        this.weiboNews.unshift(newWeibo);
        this.newWeiboContent.content = '';
        this.profileData[2].num++;
      },
      attention(index) {
        this.weiboNews[index].attention = true;    
        this.profileData[0].num++;    
      },
      unAttention(index) {
        this.weiboNews[index].attention = false;
        this.profileData[0].num--;
      },
      handleCollect(index) {
        this.weiboNews[index].collect = !this.weiboNews[index].collect;
        this.weiboNews[index].collect ? 
        this.collectNum++ :
        this.collectNum--;
      },
      handleLike(index) {
        this.weiboNews[index].like = !this.weiboNews[index].like;
        this.weiboNews[index].like ? 
        this.likeNum++ :
        this.likeNum--;
      }
    }
  }
</script>

<style lang="less">
  //css部分略
</style>

四. 項目單元測試腳本實戰

我們將以上文提到的"項目中的交互邏輯和需求"爲基礎, 爲SinaWeibo.vue編寫測試腳本, 下面我將展示測試用例編寫過程:

1.在文本框中輸入內容後點擊"發佈"按鈕(1), 會新發布內容到微博列表中, 且個人頭像等下的微博數量(6)會增加1個
it('點擊發布按鈕,發佈新內容&個人微博數量增加1個', () => {
    const wrapper = mount(SinaWeibo);
    const textArea = wrapper.find('.weibo-publish-wrapper textarea');
    const buttonOfPublish = wrapper.find('.weibo-publish-wrapper button');
    const lengthOfWeiboNews = wrapper.vm.weiboNews.length;
    const countOfMyWeibo = wrapper.vm.profileData[2].num;
    
    //設置textArea的綁定數據
    wrapper.setData({newWeiboContent: {
      imgUrl: '../../static/image/profile.jpg',
      name: 'Lee_tanghui',
      resource: '剛剛 來自 網頁版微博',
      content: '歡迎來到我的微博', 
      images: []
    }});
    //觸發點擊事件
    buttonOfPublish.trigger('click');
    const lengthOfWeiboNewsAfterPublish = wrapper.vm.weiboNews.length;
    const countOfMyWeiboAfterPublish = wrapper.vm.profileData[2].num;

    //斷言: 發佈新內容
    expect(lengthOfWeiboNewsAfterPublish).to.equal(lengthOfWeiboNews + 1);
    //斷言: 個人微博數量增加1個
    expect(countOfMyWeiboAfterPublish).to.equal(countOfMyWeibo + 1);
    
  })

測試結果:
在這裏插入圖片描述
通過測試

2.當文本框中無內容時, 不能發佈空微博到微博列表, 且彈出提示框, 叫用戶輸入內容
 it('當文本框中無內容時, 不能發佈空微博到微博列表, 且彈出提示框', () => {
    const wrapper = mount(SinaWeibo);
    const textArea = wrapper.find('.weibo-publish-wrapper textarea');
    const buttonOfPublish = wrapper.find('.weibo-publish-wrapper button');
    const lengthOfWeiboNews = wrapper.vm.weiboNews.length;
    const countOfMyWeibo = wrapper.vm.profileData[2].num;
    
    //設置textArea的綁定數據爲空
    wrapper.setData({newWeiboContent: {
      imgUrl: '../../static/image/profile.jpg',
      name: 'Lee_tanghui',
      resource: '剛剛 來自 網頁版微博',
      content: '', 
      images: []
    }});
    //觸發點擊事件
    buttonOfPublish.trigger('click');
    const lengthOfWeiboNewsAfterPublish = wrapper.vm.weiboNews.length;
    const countOfMyWeiboAfterPublish = wrapper.vm.profileData[2].num;

    //斷言: 沒有發佈新內容
    expect(lengthOfWeiboNewsAfterPublish).to.equal(lengthOfWeiboNews);
    //斷言: 個人微博數量不變
    expect(countOfMyWeiboAfterPublish).to.equal(countOfMyWeibo);
  })

測試結果:
在這裏插入圖片描述

3.當點擊"關注"(2), 個人頭像下關注的數量(5)會增加1個, 且按鈕內字體變成"取消關注"; 當點擊"取消關注"(2), 個人頭像下的數量(5)會減少1個, 且按鈕內字體變成"關注"
  it('當點擊"關注", 個人頭像下關注的數量會增加1個, 且按鈕內字體變成"取消關注"', () => {
    const wrapper = mount(SinaWeibo);
    const buttonOfAddAttendion = wrapper.find('.add');
    const countOfMyAttention = wrapper.vm.profileData[0].num;
  
    //觸發事件
    buttonOfAddAttendion.trigger('click');
    
    const countOfMyAttentionAfterClick = wrapper.vm.profileData[0].num;

    //斷言: 個人頭像下關注的數量會增加1個
    expect(countOfMyAttentionAfterClick).to.equal(countOfMyAttention + 1);
    //斷言: 按鈕內字體變成"取消關注
    expect(buttonOfAddAttendion.text()).to.equal('取消關注');
  })
  it('當點擊"取消關注", 個人頭像下關注的數量會減少1個, 且按鈕內字體變成"關注"', () => {
    const wrapper = mount(SinaWeibo);
    const buttonOfUnAttendion = wrapper.find('.cancel');
    const countOfMyAttention = wrapper.vm.profileData[0].num;
  
    //觸發事件
    buttonOfUnAttendion.trigger('click');
    
    const countOfMyAttentionAfterClick = wrapper.vm.profileData[0].num;

    //斷言: 個人頭像下關注的數量會增加1個
    expect(countOfMyAttentionAfterClick).to.equal(countOfMyAttention - 1);
    //斷言: 按鈕內字體變成"取消關注
    expect(buttonOfUnAttendion.text()).to.equal('關注');
  })

測試結果:
在這裏插入圖片描述

4.當點擊"收藏"(3)時, 我的收藏(7)會增加1個數量, 且按鈕內文字變成"已收藏"; 點擊"已收藏"(3)時, 我的收藏(7)會減少1個數量, 且按鈕內文字變成"收藏"
  it('當點擊"收藏"時, 我的收藏會增加1個數量, 且按鈕內文字變成"已收藏"', () => {
    const wrapper = mount(SinaWeibo);
    const buttonOfCollect = wrapper.find('.collectWeibo');
    const countOfMyCollect = Number(wrapper.find('.collect-num span').text());

    //觸發點擊事件
    buttonOfCollect.trigger('click');
    const countOfMyCollectAfterClick = Number(wrapper.find('.collect-num span').text());

    //斷言: 我的收藏數量會加1
    expect(countOfMyCollectAfterClick).to.equal(countOfMyCollect + 1);
    //斷言: 按鈕內文字變成已收藏
    expect(buttonOfCollect.text()).to.equal('已收藏');
  })
  it('當點擊"已收藏"時, 我的收藏會減少1個數量, 且按鈕內文字變成"收藏"', () => {
    const wrapper = mount(SinaWeibo);
    const buttonOfUnCollect = wrapper.find('.uncollectWeibo');
    const countOfMyCollect = Number(wrapper.find('.collect-num span').text());

    //觸發點擊事件
    buttonOfUnCollect.trigger('click');
    const countOfMyCollectAfterClick = Number(wrapper.find('.collect-num span').text());

    //斷言: 我的收藏數量會減1
    expect(countOfMyCollectAfterClick).to.equal(countOfMyCollect - 1 );
    //斷言: 按鈕內文字變成已收藏
    expect(buttonOfUnCollect.text()).to.equal('收藏');
  })

測試結果:
在這裏插入圖片描述

5.當點擊"贊"(4), 我的贊(8)會增加1個數量, 且按鈕內文字變成"取消贊"; 點擊"取消贊"(3)時, 我的贊(8)會減少1個數量, 且按鈕內文字變成"贊"
  it('當點擊"贊", 我的贊會增加1個數量, 且按鈕內文字變成"取消贊"', () => {
    const wrapper = mount(SinaWeibo);
    const buttonOfLike = wrapper.find('.dislikedWeibo');
    const countOfMyLike = Number(wrapper.find('.like-num span').text());

    //觸發點擊事件
    buttonOfLike.trigger('click');
    const countOfMyLikeAfterClick = Number(wrapper.find('.like-num span').text());

    //斷言: 我的贊會增加1個數量
    expect(countOfMyLikeAfterClick).to.equal(countOfMyLike + 1);
    //斷言: 按鈕內文字變成取消贊
    expect(buttonOfLike.text()).to.equal('取消贊');
  });
it('當點擊"取消贊", 我的贊會減少1個數量, 且按鈕內文字變成"贊"', () => {
    const wrapper = mount(SinaWeibo);
    const buttonOfDislike = wrapper.find('.likedWeibo');
    const countOfMyLike = Number(wrapper.find('.like-num span').text());

    //觸發點擊事件
    buttonOfDislike.trigger('click');
    const countOfMyLikeAfterClick = Number(wrapper.find('.like-num span').text());

    //斷言: 我的贊會增加1個數量
    expect(countOfMyLikeAfterClick).to.equal(countOfMyLike - 1);
    //斷言: 按鈕內文字變成取消贊
    expect(buttonOfDislike.text()).to.equal('贊');
  });

測試結果
在這裏插入圖片描述

項目地址:

Git倉庫: https://github.com/Lee-Tanghui/Vue-Testing-Demo

參考文章

測試框架 Mocha 實例教程 - 阮一峯
Chai.js斷言庫API中文文檔
知乎: 如果對vue進行單元測試
Vue.js學習系列六——Vue單元測試Karma+Mocha學習筆記
單元測試
Element
前端單元測試之Karma環境搭建
前端自動化測試是幹嘛的?
Karma官網

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章