Sinon 入門,看這篇文章就夠了

Author: bugall
Wechat: bugallF
Email: 769088641@qq.com
項目地址: https://github.com/bugall/nod...前端


爲何須要Sinon

當咱們在開發前端項目的時候, 不少時候須要根據後端返回的數據來渲染頁面, 咱們一般使用AJAX發送請求給服務端。當咱們開發後端邏輯的時候有時候須要鏈接數據庫,根據從數據庫中獲得的數據來執行後續的邏輯代碼, 或者其餘的依賴, 甚至會更加複雜棘手。這些開發都存在一個共同的侷限性, 就是會去依賴別的服務, 須要別的系統的支持。 例如, 若是咱們使用Ajax請求網絡, 您須要有一個服務器來響應對應的請求。對於數據庫, 您須要有一個爲測試設置的測試數據庫。node

全部這些都意味着編寫和運行測試更加困難, 由於您須要作額外的工做來準備和設置一個測試成功的環境。
值得慶幸的是, 咱們能夠用sinon.js避免全部麻煩。咱們能夠利用它的特性將上面的例子簡化爲幾行代碼。git

然而, 第一次接觸spies, stub, mock可能棘手。它可能很難選擇何時用什麼功能。它們也有一些問題,因此你須要知道你應該用什麼功能解決什麼樣的問題。
在這篇文章中將向你展現spies, stub, mock什麼時候以及如何使用它們,並給你一套最佳實踐,幫助您避免常見的陷阱.github

什麼是Sinon

Sinon具備獨立的spies, stub, mock功能,Sinon並非獨立的測試框架,它只是在測試中提供了上述的三種功能, 例如咱們經常使用的測試框架Mocha,Sinon並不能徹底替代Mocha的功能。數據庫

Sinon經過所謂的測試替代(test-double)輕鬆消除測試的複雜度,
測試替代,顧名思義,測試中用到的是真實代碼邏輯的替代品。回過頭來看Ajax示例,咱們不須要設置服務器,而是用Ajax的替代代碼,咱們把Ajax的邏輯替換成不須要經過請求服務器就返回預先設置好的數據,這聽起來有難以想象,可是基本概念很簡單。由於JavaScript是動態的,因此咱們能夠在調用某個方法的時候使用任何函數來替換它。在Sinon中們能夠用一個測試邏輯取代任何JavaScript函數,而後讓測試複雜的事情變的簡單化。後端

spies的概念

顧名思義,spies咱們乾脆就把它稱做間諜函數好了,間諜函數是Sinon最簡單的部分,其它的功能都是創建在spies之上的,spies的主要用途是收集有關函數調用的信息。您還可使用它們來幫助驗證事物,例如是否調用了函數等。就像電影《竊聽風雲》中同樣,監聽房間內都有那些人進出,作了什麼事,並且這個監聽過程是不會房間內的人感知的。一樣spies的實現監聽的基礎上是不會影響函數自己的正常調用(被監聽的函數的上下文關係不會被影響)。固然咱們實現是須要在房間裏偷偷的安裝竊聽器的, 那麼spies的竊聽器是如何實現的呢?後文咱們有介紹服務器

stub的概念

他們擁有spies的全部功能,不是監視某個函數的調用狀況,而是徹底取代了這個函數。換句話說,當使用spies時,原始函數仍然運行,可是當使用stub時,函數將不具備原始的功能,而是替換後的函數。網絡

mock的概念

mockstub的功能同樣都是用來替換指定的函數,若是你想替換掉一個對象中的多個方法,這時mock就能夠發揮做用了,可是若是僅僅是替換對象中的一個函數,那麼stub更加簡單易用,當咱們使用mock的時候應該十分當心,由於大量的替換原有代碼邏輯,會致使test變的脆弱app


Sinon的使用場景

spies

正如名字所暗示的,spies被用來獲取關於函數調用的信息。例如,一個spies能夠告訴咱們調用一個函數的次數、每次調用的參數、返回的值、拋出的錯誤等。所以,當測試的目的是驗證發生的事情時,間諜是一個很好的選擇。結合Sinon的說法,咱們能夠經過一個簡單的spies檢查不一樣的結果。框架

間諜最多見的場景包括:

  • 檢查函數被調用了多少次

it('should call save once', function() {
  var save = sinon.spy(Database, 'save');

  setupNewUser({ name: 'test' }, function() { });

  save.restore();
  sinon.assert.calledOnce(save);
});
  • 檢查傳遞給函數的參數

it('should pass object with correct values to save', function() {
  var save = sinon.spy(Database, 'save');
  var info = { name: 'test' };
  var expectedUser = {
    name: info.name,
    nameLowercase: info.name.toLowerCase()
  };

  setupNewUser(info, function() { });

  save.restore();
  sinon.assert.calledWith(save, expectedUser);
});

stub

存根就像間諜,除了它們替換目標功能。它們還能夠包含自定義行爲,例如返回值,或拋出異常。他們甚至能夠自動調用做爲參數提供的任何回調函數。

存根有幾個經常使用的用途:

  • 您可使用它們來代替有問題的代碼段

  • 您可使用它們來觸發不會觸發的代碼路徑,例如錯誤處理

  • 您可使用它們來幫助測試異步代碼更容易

  • 存根可用於替代有問題的代碼,即便寫入測試困難的代碼。這一般是外部網絡鏈接,數據庫或其餘非JavaScript引發的。這些問題是它們常常須要手動設置。例如,在運行測試以前,咱們須要填寫一個帶有測試數據的數據庫,這使得運行和寫入更復雜。

it('should pass object with correct values to save', function() {
  var save = sinon.stub(Database, 'save');
  var info = { name: 'test' };
  var expectedUser = {
    name: info.name,
    nameLowercase: info.name.toLowerCase()
  };

  setupNewUser(info, function() { });

  save.restore();
  sinon.assert.calledWith(save, expectedUser);
});

經過用stub替換與數據庫相關的功能,咱們再也不須要實際的數據庫進行測試。 幾乎任何狀況下,相似的方法均可以用於其餘難以測試的代碼。

存根也可用於觸發不一樣的代碼路徑。 若是咱們測試的代碼調用另外一個函數,咱們有時須要測試它在異常條件下的行爲, 咱們可使用存根從代碼中觸發錯誤:

it('should pass the error into the callback if save fails', function() {
  var expectedError = new Error('oops');
  var save = sinon.stub(Database, 'save');
  save.throws(expectedError);
  var callback = sinon.spy();

  setupNewUser({ name: 'foo' }, callback);

  save.restore();
  sinon.assert.calledWith(callback, expectedError);
});

mock

主要用於當你存根的時候想驗證多個具體的行爲時

例如,如下是咱們如何使用mock驗證更具體的數據庫保存方案:

it('should pass object with correct values to save only once', function() {
  var info = { name: 'test' };
  var expectedUser = {
    name: info.name,
    nameLowercase: info.name.toLowerCase()
  };
  var database = sinon.mock(Database);
  database.expects('save').once().withArgs(expectedUser);

  setupNewUser(info, function() { });

  database.verify();
  database.restore();
});

Sinon的實現原理

spies

const sinon = {
  spyObjs: {},
  spy: function(obj, method) {
    const self = this;
    this.spyObjs['spy#:' + (Object.keys(self.spyObjs).length + 1)] = {}
    this.proxy(obj, method);
  }, proxy: function(obj, method) {
    const descriptor = Object.getOwnPropertyDescriptor(obj, method);
    const delegateFlag = 'spy#:' + Object.keys(sinon.spyObjs).length;
    this.spyObjs[delegateFlag] = {
      delegateValue: descriptor.value,
      delegateObject: obj
    }
    Object.defineProperty(obj, method, Object.getOwnPropertyDescriptor(this, 'invoke'))
  }, invoke: function(name) {
    console.log('參數%s, 被調用了', name)
    const delegateFlag = 'spy#:' + Object.keys(sinon.spyObjs).length;
    sinon.spyObjs[delegateFlag].delegateValue.apply(sinon.spyObjs[delegateFlag].delegateObject)
  }
}

var testFlag = {
  sayHello: function(name) {
    console.log('Hello:%s', name)
  }, whoAmI: function() {
    this.sayHello('bugall')
    console.log('Who am i')
  }
}
sinon.spy(testFlag, 'whoAmI');

testFlag.whoAmI('bugall')
相關文章
相關標籤/搜索