在使用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 執行過程
- 執行
npm run unit
命令 - 開啓Karma運行環境
- 使用Mocha去逐個測試用Chai斷言寫的測試用例
- 在終端顯示測試結果
- 如果測試成功,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()的報錯
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官網