使用mocha,chai和sinon測試你的前端js代碼

特別聲明:此篇文章是通過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文件在瀏覽器打開各淀,你將看到一個測試通過的頁面如下:

Paste_Image.png

如果有任何斷言達不到期望懒鉴,你會在測試結(jié)果中被提示,例如我們將greets函數(shù)稍微改寫:

  Cow.prototype = {
    greets: function(target) {
      if (!target)
        throw new Error("missing target");
      return this.name + " greets " + target + "!";
    }
  };

你將會看到:

Paste_Image.png

那我們又如何測試異步調(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會用紅色的字標出那些可能被懷疑成超時操作的時間:

Paste_Image.png

使用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對象的logerror方法甸饱,以確保我們是否調(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")
    });
  });
});

這里有幾點需要留意:

  • beforeEachafterEach是mocha api的一部分沦童,它們確保你可以在測試的開始與銷毀的時候做一些相關的操作
  • sinon提供了沙盒機制,基本上你可以定義和附帶一系列的存根到一個沙盒對象上叹话,且該對象可以在某個時刻重置之前暫存過的函數(shù)方法
  • 當函數(shù)方法被暫存(stub)偷遗,真實的方法不會再被調(diào)用,所以在瀏覽器里的控制臺里顯然不會有任何錯誤信息被打印出來
  • sinon內(nèi)部自己封裝了一套斷言語法:sinon.assert驼壶,當然它也有專門為chai制定的插件:sinon-chai氏豌,有興趣的可以去看下哈

有關mocha, chai和sinon的更多功能和技巧你可以關注它們各自的站點,在這篇文章里我無法全部覆蓋热凹,但我希望你可以帶著一種求賢若渴的心態(tài)去對前端的單元測試一探究竟泵喘。愿單測愉快~

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市般妙,隨后出現(xiàn)的幾起案子纪铺,更是在濱河造成了極大的恐慌,老刑警劉巖碟渺,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鲜锚,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機芜繁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門旺隙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人骏令,你說我怎么就攤上這事蔬捷。” “怎么了榔袋?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵抠刺,是天一觀的道長。 經(jīng)常有香客問我摘昌,道長速妖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任聪黎,我火速辦了婚禮罕容,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘稿饰。我一直安慰自己锦秒,他們只是感情好,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布喉镰。 她就那樣靜靜地躺著旅择,像睡著了一般。 火紅的嫁衣襯著肌膚如雪侣姆。 梳的紋絲不亂的頭發(fā)上生真,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天,我揣著相機與錄音捺宗,去河邊找鬼柱蟀。 笑死,一個胖子當著我的面吹牛蚜厉,可吹牛的內(nèi)容都是我干的长已。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼昼牛,長吁一口氣:“原來是場噩夢啊……” “哼术瓮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起贰健,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤胞四,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后霎烙,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撬讽,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡蕊连,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了游昼。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甘苍。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖烘豌,靈堂內(nèi)的尸體忽然破棺而出载庭,到底是詐尸還是另有隱情覆糟,我是刑警寧澤卧斟,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站蓖谢,受9級特大地震影響标锄,放射性物質(zhì)發(fā)生泄漏顽铸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一料皇、第九天 我趴在偏房一處隱蔽的房頂上張望谓松。 院中可真熱鬧,春花似錦践剂、人聲如沸鬼譬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽优质。三九已至,卻和暖如春军洼,著一層夾襖步出監(jiān)牢的瞬間巩螃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工歉眷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留牺六,地道東北人颤枪。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓汗捡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親畏纲。 傳聞我的和親對象是個殘疾皇子扇住,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容