特別聲明:此篇文章是通過Nicolas的博文Testing your frontend JavaScript code using mocha, chai, and sinon進行翻譯锤灿,整個譯文帶有我自己的理解與思想盈包。如需轉(zhuǎn)載此譯文,需注明英文出處:https://nicolas.perriault.net/code/2013/testing-frontend-javascript-code-using-mocha-chai-and-sinon/以及作者相關信息
——作者:Nicolas
——譯者:?Chester723
隨著網(wǎng)絡應用的豐富度與復雜程度的日益增長,如果你要同時保持一顆清醒的頭腦來應對袭艺,你就需要利用單元測試來檢驗你的前端js代碼
在過去的四個月里匠璧,我一直在為Mozilla做著某個大項目衙解,其中也不乏遇到過有關單測的一些技巧喉前。雖然我們有嘗試在這方面使用CasperJS來實現(xiàn),但是Firefox在當時并不被支持而且我們還需要考慮到js引擎的兼容性問題轻专,為此忆矛,我們放棄了它轉(zhuǎn)而使用Mocha, Chai, Sinon這三個被認為目前最好的單測工作流。
mocha測試框架與chai expectation庫
Mocha本身是一個單元測試框架而chai則是一個被稱之為期望
函數(shù)的庫铭若。為了能夠更直觀的說明兩者的關系洪碳,我們可以把mocha比作一個用于描述單元測試的組件,而chai則可以通過便利的輔助函數(shù)設立斷言來判斷我們的代碼執(zhí)行結(jié)果是否有達到預期叼屠。
例如瞳腌,現(xiàn)在我們有一個Cow
對象要被用于單元測試:
// cow.js
(function(exports) {
"use strict";
function Cow(name) {
this.name = name || "Anon cow";
}
exports.Cow = Cow;
Cow.prototype = {
greets: function(target) {
if (!target)
throw new Error("missing target");
return this.name + " greets " + target;
}
};
})(this);
乍這么看上去好像沒什么特別的地方,但我們依舊要對它測試一下镜雨。
mocha和chai都可以被用在node環(huán)境與瀏覽器環(huán)境里嫂侍,對于后者而言,我們需要構建一個測試用的html頁面并配合?以下幾個庫:
我個人的建議是把這些第三方的庫都放在一個叫做vendor
的子文件夾里。讓我們來構建一個html文件來測試我們的代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Cow tests</title>
<link rel="stylesheet" media="all" href="vendor/mocha.css">
</head>
<body>
<div id="mocha"><p><a href=".">Index</a></p></div>
<div id="messages"></div>
<div id="fixtures"></div>
<script src="vendor/mocha.js"></script>
<script src="vendor/chai.js"></script>
<script src="cow.js"></script>
<script>mocha.setup('bdd')</script>
<script src="cow_test.js"></script>
<script>mocha.run();</script>
</body>
</html>
請注意這里我們將使用到Chai's BDD Expect API這種方式挑宠,因此我們會有類似mocha.setup('bdd')
這樣的調(diào)用菲盾。
現(xiàn)在讓我們?yōu)橹暗?code>Cow對象寫一個簡單的測試組件并把它保存到cow_test.js
文件中:
//cow_test.js
var expect = chai.expect;
describe("Cow", function() {
describe("constructor", function() {
it("should have a default name", function() {
var cow = new Cow();
expect(cow.name).to.equal("Anon cow");
});
it("should set cow's name if provided", function() {
var cow = new Cow("Kate");
expect(cow.name).to.equal("Kate");
});
});
describe("#greets", function() {
it("should throw if no target is passed in", function() {
expect(function() {
(new Cow()).greets();
}).to.throw(Error);
});
it("should greet passed target", function() {
var greetings = (new Cow("Kate")).greets("Baby");
expect(greetings).to.equal("Kate greets Baby");
});
});
});
不出什么意外,如果你把html文件在瀏覽器打開各淀,你將看到一個測試通過的頁面如下:
如果有任何斷言達不到期望
懒鉴,你會在測試結(jié)果中被提示,例如我們將greets函數(shù)稍微改寫:
Cow.prototype = {
greets: function(target) {
if (!target)
throw new Error("missing target");
return this.name + " greets " + target + "!";
}
};
你將會看到:
那我們又如何測試異步調(diào)用的代碼碎浇?
試想下我們有一個叫做Cow#lateGreets
的函數(shù)临谱,它會在一秒的延遲之后執(zhí)行:
Cow.prototype = {
greets: function(target) {
if (!target)
throw new Error("missing target");
return this.name + " greets " + target + "!";
},
lateGreets: function(target, cb) {
setTimeout(function(self) {
try {
cb(null, self.greets(target));
} catch (err) {
cb(err);
}
}, 1000, this);
}
};
當然我們同樣可以對它進行測試,mocha內(nèi)部提供的done
函數(shù)早就為我們準備好了一切:
describe("#lateGreets", function() {
it("should pass an error if no target is passed", function(done) {
(new Cow()).lateGreets(null, function(err, greetings) {
expect(err).to.be.an.instanceof(Error);
done();
});
});
it("should greet passed target after one second", function(done) {
(new Cow("Kate")).lateGreets("Baby", function(err, greetings) {
expect(greetings).to.equal("Kate greets Baby");
done();
});
});
});
很方便的是奴璃,mocha會用紅色的字標出那些可能被懷疑成超時操作的時間:
使用Sinon來偽造或者模擬真實環(huán)境
當你在進行單元測試的時候悉默,你可能不希望將其依賴于其他的類庫,這種依賴性很可能會使你寫的函數(shù)產(chǎn)生一定的副作用苟穆,而sinon在擁有存根
和模擬
類庫操作的兩大功能的幫助下抄课,可以極大的減少這種副作用。
舉個栗子雳旅,我們試想下在之前的Cow#greets
方法中跟磨,如果我們把原來本該在拋出異常返回字符串的情況改為通過調(diào)用window.console
的error方法:
// cow.js
(function(exports) {
"use strict";
function Cow(name) {
this.name = name || "Anon cow";
}
exports.Cow = Cow;
Cow.prototype = {
greets: function(target) {
if (!target)
return console.error("missing target");
console.log(this.name + " greets " + target);
}
};
})(this);
這種情況我們又如何對其測試呢?好在sinon已經(jīng)提供好了解決方案岭辣。首先吱晒,讓我們先引入sinon的js代碼:
<script src="vendor/mocha.js"></script>
<script src="vendor/chai.js"></script>
<script src="vendor/sinon-1.7.1.js"></script>
首先我們先暫存console
對象的log
和error
方法甸饱,以確保我們是否調(diào)用了它們以及什么參數(shù)被傳入進去:
var expect = chai.expect;
describe("Cow", function() {
var sandbox;
beforeEach(function() {
// create a sandbox
sandbox = sinon.sandbox.create();
// stub some console methods
sandbox.stub(window.console, "log");
sandbox.stub(window.console, "error");
});
afterEach(function() {
// restore the environment as it was before
sandbox.restore();
});
// ...
describe("#greets", function() {
it("should log an error if no target is passed in", function() {
(new Cow()).greets();
sinon.assert.notCalled(console.log);
sinon.assert.calledOnce(console.error);
sinon.assert.calledWithExactly(console.error, "missing target")
});
it("should log greetings", function() {
var greetings = (new Cow("Kate")).greets("Baby");
sinon.assert.notCalled(console.error);
sinon.assert.calledOnce(console.log);
sinon.assert.calledWithExactly(console.log, "Kate greets Baby")
});
});
});
這里有幾點需要留意:
-
beforeEach
和afterEach
是mocha api的一部分沦童,它們確保你可以在測試的開始與銷毀的時候做一些相關的操作 - sinon提供了沙盒機制,基本上你可以定義和附帶一系列的存根到一個沙盒對象上叹话,且該對象可以在某個時刻重置之前暫存過的函數(shù)方法
- 當函數(shù)方法被暫存(stub)偷遗,真實的方法不會再被調(diào)用,所以在瀏覽器里的控制臺里顯然不會有任何錯誤信息被打印出來
- sinon內(nèi)部自己封裝了一套斷言語法:
sinon.assert
驼壶,當然它也有專門為chai制定的插件:sinon-chai氏豌,有興趣的可以去看下哈
有關mocha, chai和sinon的更多功能和技巧你可以關注它們各自的站點,在這篇文章里我無法全部覆蓋热凹,但我希望你可以帶著一種求賢若渴的心態(tài)去對前端的單元測試一探究竟泵喘。愿單測愉快~