使用mocha和chai做nodejs單元測試

本文介紹一下nodejs中常見的單元測試包及其使用

一、nodejs單元測試包簡介

  1. nodejs中最負盛名的單元測試框架是mocha,據(jù)官方資料,它已經(jīng)被超過10萬個npm包所依賴。其擁有豐富的,可配置和可擴展的測試特性。mocha默認使用nodejs內(nèi)置的斷言庫assert,但更好的選擇是使用第三方的斷言庫,根據(jù)單元測試和業(yè)務的需要。
  2. chia第三方斷言庫,支持各種斷言風格: expect,assert,should,詳見其官方文檔。
  3. sinon用于測試stubs和mocks。
  4. rewire用于重寫包引入機制, 使用得我們可以測試module中的私有方法。
  5. supertest用于測試web項目,模擬request請求。

三、mocha

mocha的使用非常簡單,官方文檔上有詳細的說明,但此處還是做一些簡單的說明。

  1. 安裝(推薦安裝成全局的npm)
$ npm install --global mocha

或者將依賴寫入了package.json中的devDependencies,例如:

{
  "devDependencies": {
    "mocha": "^3.2.0",
    "chai": "^3.5.0",
    "co": "^4.6.0",
    "rewire": "^2.5.2",
    "sinon": "^1.17.7"
  }
}
  1. 一個簡單的測試用例(來自官網(wǎng))
    創(chuàng)建一個名為test的目錄,在目錄中新建test.js(~/nodejs/test/test.js)文件,輸入以下代碼:
var assert = require('assert');
describe('Array', function() {
  describe('#indexOf()', function() {
    it('should return -1 when the value is not present', function() {
      assert.equal(-1, [1,2,3].indexOf(4));
    });
  });
});

命令行中執(zhí)行:

peachcat@peachcat:~/nodejs $ mocha


  Array
    #indexOf()
      ? should return -1 when the value is not present


  1 passing (34ms)
  1. 一個異步調(diào)用的測試例子(來自官方)
    異步調(diào)用完成之后需要調(diào)用mocha提供的callback,以完成此測試:
describe('User', function() {
  describe('#save()', function() {
    it('should save without error', function(done) {
      var user = new User('Luna');
      user.save(done);
    });
  });
});
  1. Promise:除了直接調(diào)用mocha的callback之外,還可以直接返回一個promise
const assert = require('assert');
                               
it('should complete this test', function () {
  return new Promise(function (resolve, reject) {
    assert.ok(true);           
    resolve();                 
  });
});
  1. 和generator一起使用,generator既沒有callback,也沒有返回promise,所以mocha無法直接處理,但我們可以使用co來執(zhí)行generator,并且co返回的就是promise。例子:
'use strict';
var fs = require("fs") ;       
let co = require('co');        
var assert = require('assert');

function readFile(path){       
  return new Promise(function(resolve, reject){
    fs.readFile(path, "utf8", function(err, data){
      if(err){                 
        reject(err);           
      }else{
        resolve(data);         
      }
    });
  });
}   
    
describe("Generator", function(){
  it('test with co', function () {
    return co(function*(){     
      let txt = yield readFile("a.txt");
      assert.equal(txt, "file A\n");  
    });
  });
});
  1. mocha支持before(), after(), beforeEach(), and afterEach()這類方法,用于設置預置的測試條件和清理測試之后的資源。使用方式如下:
describe('hooks', function() {
  before(function() {
    // runs before all tests in this block
  });
  after(function() {
    // runs after all tests in this block
  });
  beforeEach(function() {
    // runs before each test in this block
  });
  afterEach(function() {
    // runs after each test in this block
  });
  // test cases
});

三、chia

chia支持多種寫法: Should,Expect,Assert,這里主要使用的是expect方式,官方有個大概的例子:

var expect = chai.expect;

expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(foo).to.have.lengthOf(3);
expect(tea).to.have.property('flavors').with.lengthOf(3);
  1. equal和eql

equal: 使用"==="比較兩個值是否相等,當值為引用類型時,這只能比較引用的地址是否相等,若兩個對象內(nèi)容相同,但引用地址不同,即為兩個對象時,使用equal時,斷言會失敗。

expect({ foo: 'bar' }).to.not.equal({ foo: 'bar' });

使用deep可以對比兩個對象的內(nèi)容,如:

expect({ foo: 'bar' }).to.deep.equal({ foo: 'bar' });

eql: 等價于deap.equal,如:

expect({ foo: 'bar' }).to.eql({ foo: 'bar' });
expect([ 1, 2, 3 ]).to.eql([ 1, 2, 3 ]);
  1. 異常斷言

使用".throw"可以斷言會拋出異常的方法,但是直接調(diào)用方法時,會拋出異常,導到測試直接失敗,所以需要對會拋出異常的方法進行包裝。比如:
m.js

module.exports.throwError = function(a){
  if( a > 100){                
    throw new ReferenceError('This is a bad function.');;
  } else {
    return a * 10;
  }
};

測試代碼:

'use strict';

let expect = require('chai').expect;
let m = require("../m");

describe("Chia throw test", function(){
  it("should throw ReferenceError", function(){
    expect(m.throwError(101)).to.throw(ReferenceError);
  });
});

此測試用例無法通過:

peachcat@peachcat:~/nodejs/mocha $ mocha test/m.js 


  Chia throw test
    1) should throw ReferenceError


  0 passing (57ms)
  1 failing

  1) Chia throw test should throw ReferenceError:
     AssertionError: expected [Function] to throw ReferenceError
      at Context.<anonymous> (test/m.js:8:34)

將throwError 方法包裝起來,修改之后的代碼如下:

'use strict';

let expect = require('chai').expect;
let m = require("../m");

describe("Chia throw test", function(){
  it("should throw ReferenceError", function(){
    let warpper = function(){ m.throwError(101); }
    expect(warpper).to.throw(ReferenceError);
  });     
});

再次跑測試,順利通過:

peachcat@peachcat:~/nodejs/mocha $ mocha test/m.js 


  Chia throw test
    ? should throw ReferenceError


  1 passing (51ms)

從上面的代碼來看,會拋出異常的方法,是在expect中被調(diào)用的。

generator方法中拋出異常: generator方法無法在expect中被執(zhí)行,即使使用了包裝方法也無法調(diào)用,因此generator檢測拋出異常時,可以使用下面的方式,使用co調(diào)用generator方法,在catch中獲得異常,并對此異常進行斷言。例子如下:

'use strict';
let co = require('co');        
let expect = require('chai').expect;

function* generatorMethod(a){  
  if (a > 10){                 
    throw new Error("Generator Error");
  } else {
    return a * 10;
  }
}   
    
describe("Generator", function(){
  it('test throw Error', function () {
    return co(function*(){     
      let results = yield generatorMethod(11);
    }).catch(function(err){
      expect(err.message).to.equal("Generator Error");
    });
  });
});

四、sinon

sinon可以做許多事情,比如: stub, mock,可以模擬ajax請求等等。這里我們主要使用了stub功用。下面就stub中的幾種使用過的情況做下說明:

  1. stub一個方法,下面的代碼,將返回一個stub方法替代object中的"method"方法
var stub = sinon.stub(object, "method");
  1. stub一個方法,并且在使用指定參數(shù)被調(diào)用時,返回指定的數(shù)據(jù)。
var stub = sinon.stub(object, "method");
stub.withArgs(42).returns(1);
stub.withArgs(1).throws("TypeError");

object.method(42);  // return 1
object.method(1);   // throws TypeError
  1. 使用sandbox隔離stub,nodejs中模塊,在全局上是同一個對象,因此對某個模塊進行了stub,后面的測試還需要使用此模塊時會相互影響,因此可以使用sinon的sandbox功能,將stub進行隔離。例如:
describe('get_all_miss_jsons', function(){
  it("#getAllMissJsons", sinon.test(function(){
    //使用this.stub替代全局的sinon.stub
    this.stub(missJsonService, "fetch").returns(someObject); 

    //assert
  }));
});
  1. spies、stub和mock的區(qū)別
    sinon中提供了幾種有用的輔助測試功能,spies,stub和mock,下面說明這3種方式的意義和適合場景。
定義 適合場景
spies spy是一個方法,測試時它會記錄下每一次此方法被調(diào)用時的參數(shù),返回值,或者拋出的異常。spy方法可以一個匿名方法,或者它可以包裝一個已存在的方法。 在測試callback和了解某個特定的方法在整個測試中是怎么被使用的是非常有用的。spy也可以包裝一個已有方法,同樣可以統(tǒng)計其調(diào)用情況,例子。見表下方的spy例子。
stub stub是一個預先編寫的方法,用于替代某個被測試的方法。stub的使用場景如下 1.單元測試中控制一個方法,強制其走到預置的代碼路徑;包括強制一個方法拋出異常以測試出錯的情況。2.避免一個方法被直接調(diào)用(比如:邏輯太復雜,和本次測試無關的方法;調(diào)用耗時太長的方法等)。
mock mock,類似于stub,同樣是一個預置的方法,同樣用于在測試中替代某個被測試的方法。與stub不同的是,mock方法在測試中必須被調(diào)用,否則測試用例失敗。 1. mock應該只被用于有單元測試的方法上。如果你想控制你的單元測試是怎么被使用,并且在方法被真實調(diào)用之前,請使用mock。2. mock有內(nèi)置的斷言,可能讓你的測試失敗。如果你不用在某個特殊調(diào)用上使用斷言,就沒必要使用mock。另外,一個獨單的測試中,不要使用超過1個以上的mock。

一個spy的例子:

"test should call subscribers on publish": function () {
    var callback = sinon.spy();    //假裝一個callback
    PubSub.subscribe("message", callback);

    PubSub.publishSync("message");

    assertTrue(callback.called); //確定此方法做為回調(diào)方法被調(diào)用了。
}

五、rewire

nodejs模塊中,有許多私有方法,即未通過module.exports導出的方法,可以通過rewire這個npm包提供的方法rewire替換require,就有辦法可以訪問這些私有方法,從而對其做單元測試。例如:

  1. 在模塊m.js中,存在私有方法_add
'use strict';

function _add(a, b){
  return a + b;
};

module.exports.add = function(a, b){
  if (a > 100 ){
    a = a * 2;
  }
  return _add(a, b);
};
  1. 使用rewire測試此方法:
let rewire = require('rewire');
let m      = rewire("../m");
var expect = require('chai').expect;

describe("rewire", function(){
  it("test private method in module `m`" ,function(){
    let _add = m.__get__('_add');
    expect(_add(4, 3)).to.eq(7);
  }); 
});
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容