開啟JavaScript測試之路--Jasmine

初識Jasmine

<small><small>Jin Sun, January 17, 2016</small></small>

我們要聊些什么:

  1. 一個不錯的引子
  2. 簡單粗暴的介紹
  3. 那么我們開始吧
  4. 我該如何使用呢
  5. 與KnockoutJS不得不說的故事

引子

我們不缺乏解決問題的能力,我們?nèi)鄙俚闹皇歉绨l(fā)現(xiàn)問題的方法,而自動化測試可以幫助我們,所以讓我們歡迎 <big><big>Jasmine</big></big>.

介紹

Jasmine 是一款 JavaScript 測試框架,它不依賴于其他任何 JavaScript 組件。它有干凈清晰的語法,讓你可以很簡單的寫出測試代碼。

開始

  1. 前往 Jasmine 官網(wǎng)下載standalone版本。

    image
    image

  2. 將jasmine-standalone-xxx.zip解壓,運行SpecRunner.html,你會看到下面的界面:


    image
    image
  3. 打開SpecRunner.html,我們看看它的用法:

<html>
<head>
    <meta charset="utf-8">
    <title>Jasmine Spec Runner v2.4.1</title>

    <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.4.1/jasmine_favicon.png">
    <link rel="stylesheet" href="lib/jasmine-2.4.1/jasmine.css">
    <!-- 測試界面css樣式 -->
    <script src="lib/jasmine-2.4.1/jasmine.js"></script>
    <!-- 核心文件用于執(zhí)行單元測試的類庫 -->
    <script src="lib/jasmine-2.4.1/jasmine-html.js"></script>
    <!-- 用于顯示單元測試結(jié)果的類庫 -->
    <script src="lib/jasmine-2.4.1/boot.js"></script>
    <!-- 用于初始化單元測試所需的執(zhí)行環(huán)境類庫 -->

    <!-- include source files here... -->
    <script src="src/Player.js"></script>
    <script src="src/Song.js"></script>

    <!-- include spec files here... -->
    <script src="spec/SpecHelper.js"></script>
    <script src="spec/PlayerSpec.js"></script>

</head>
<body>
</body>
</html>

使用

打開測試文件PlayerSpec.js,我們會看到describe,it,beforeEach,afterEach,expect,toBe....都是些什么意思呢?

Jasmine有四個核心概念:分組(Suites)、用例(Specs)、期望(Expectations)、匹配(Matchers).

Suites

Suites可以理解為一組測試用例,使用全局的Jasmin函數(shù)describe 創(chuàng)建。describe函數(shù)接受兩個參數(shù),一個字符串和一個函數(shù)。字符串是這個Suites的名字或標題(通常描述下測試內(nèi)容),函數(shù)是實現(xiàn)Suites的代碼塊。

Specs

Specs可以理解為一個測試用例,使用全局的Jasmin函數(shù)it創(chuàng)建。和describe一樣接受兩個參數(shù),一個字符串和一個函數(shù),函數(shù)就是要執(zhí)行的測試代碼,字符串就是測試用例的名字。一個Spec可以包含多個expectations來測試代碼。

Expectations

Expectations由expect 函數(shù)創(chuàng)建。接受一個參數(shù)。和Matcher一起聯(lián)用,設置測試的預期值。

在分組(describe)中可以寫多個測試用例(it),也可以再進行分組(describe),在測試用例(it)中定義期望表達式(expect)和匹配判斷(toBe**)??匆粋€簡單的Demo:

describe("A suite", function() {//suites
    var a;
    it("A spec", function() {//spec
      a = true;
      expect(a).toBe(true);//expectations
    });

    describe("a suite", function() {//inner suites
           it("a spec", function() {//spec
           expect(a).toBe(true);//expectations
        });
  });
});

Matchers

Matcher實現(xiàn)一個“期望值”與“實際值”的對比,如果結(jié)果為true,則通過測試,反之,則失敗。每一個matcher都能通過not執(zhí)行否定判斷。

簡單的matchers:

expect(a).toBe(true);//期望變量a為true
expect(a).toEqual(true);//期望變量a等于true
expect(a).toMatch(/reg/);//期望變量a匹配reg正則表達式,也可以是字符串
expect(a.foo).toBeDefined();//期望a.foo已定義
expect(a.foo).toBeUndefined();//期望a.foo未定義
expect(a).toBeNull();//期望變量a為null
expect(a.isMale).toBeTruthy();//期望a.isMale為真
expect(a.isMale).toBeFalsy();//期望a.isMale為假
expect(true).toEqual(true);//期望true等于true
expect(a).toBeLessThan(b);//期望a小于b
expect(a).toBeGreaterThan(b);//期望a大于b
expect(a).toThrowError(/reg/);//期望a方法拋出異常,異常信息可以是字符串、正則表達式、錯誤類型以及錯誤類型和錯誤信息
expect(a).toThrow();//期望a方法拋出異常
expect(a).toContain(b);//期望a(數(shù)組或者對象)包含b

其他matchers:

jasmine.any(Class)--傳入構(gòu)造函數(shù)或者類返回數(shù)據(jù)類型作為期望值,返回true表示實際值和期望值數(shù)據(jù)類型相同:

it("matches any value", function() {
    expect({}).toEqual(jasmine.any(Object));
    expect(12).toEqual(jasmine.any(Number));
});

jasmine.anything()--如果實際值不是null或者undefined則返回true:

it("matches anything", function() {
    expect(1).toEqual(jasmine.anything());
});

jasmine.objectContaining({key:value})--實際數(shù)組只要匹配到有包含的數(shù)值就算匹配通過:

foo = {
      a: 1,
      b: 2,
      bar: "baz"
};
expect(foo).toEqual(jasmine.objectContaining({bar: "baz"}));

jasmine.arrayContaining([val1,val2,...])--stringContaining可以匹配字符串的一部分也可以匹配對象內(nèi)的字符串:

expect({foo: 'bar'}).toEqual({foo: jasmine.stringMatching(/^bar$/)});
expect('foobarbaz').toEqual({foo: jasmine.stringMatching('bar')});

<small>Jasmine還支持自定義Matchers,今天我們先不展開了.</small>

Setup and Teardown

為了在復雜的測試用例中更加便于組裝和拆卸,Jasmine提供了四個函數(shù):

beforeEach(function)  //在每一個測試用例(it)執(zhí)行之前都執(zhí)行一遍beforeEach函數(shù);
afterEach(function)  //在每一個測試用例(it)執(zhí)行完成之后都執(zhí)行一遍afterEach函數(shù);
beforeAll(function)  //在所有測試用例執(zhí)行之前執(zhí)行一遍beforeAll函數(shù);
afterAll(function)  //在所有測試用例執(zhí)行完成之后執(zhí)行一遍afterAll函數(shù);

對照一下結(jié)果看一下下面的例子就一目了然啦:

describe("A spec using beforeEach and afterEach", function() {
  var foo = 0;
  beforeEach(function() {
    foo += 1;

    le.log("I am beforEach");
  });
  afterEach(function() {
    foo = 0;
    console.log("I am afterEach");
  });
  it("A spec1", function() {
    expect(foo).toEqual(1);
    console.log("I am spec1");
  });
  it("A spec2", function() {
    expect(foo).toEqual(1);
    expect(true).toEqual(true);
    console.log("I am spec2");
  });
});
describe("A spec using beforeAll and afterAll", function() {
  var foo;
  beforeAll(function() {
    foo = 1;
    console.log("I am beforAll");
  });
  afterAll(function() {
    foo = 0;
    console.log("I am afterAll");
  });
  it("A spec1", function() {
    expect(foo).toEqual(1);
    foo += 1;
    console.log("I am A spec1");
  });
  it("A spec2", function() {
    expect(foo).toEqual(2);
    console.log("I am A spec2");
  });
});

最終輸出:


image
image

Suites禁用和Specs掛起

Jasmine提供xdescrib和xit方法用于屏蔽測試用例,xdescribe里面的定義的it、beforeEach、afterEach等之類的方法不會執(zhí)行,describe里面定義的xit不會執(zhí)行。
測試文檔遇到用例掛起的地方就不會執(zhí)行期望的匹配表達式,有三種使用方法:

describe("Pending specs", function() {
  xit("can be declared 'xit'", function() {//第一種,使用xit將測試用例直接屏蔽
    expect(true).toBe(false);
  });
 it("can be declared with 'it' but without a function");//第二種,只聲明it,不定義回調(diào)函數(shù)
 it("can be declared by calling 'pending' in the spec body", function() {
    expect(true).toBe(false);
    pending('this is why it is pending');//第三種,使用pending函數(shù)
  });
});

Spy追蹤

Jasmine具有函數(shù)的追蹤和反追蹤的雙重功能,這東西就是Spy!
Spy能夠存儲任何函數(shù)調(diào)用記錄和傳入的參數(shù),Spy只存在于describe和it中,在spec執(zhí)行完之后銷毀。說的這么晦澀,還是直接上例子吧:

describe("A spy", function() {
  var foo, bar = null;
  beforeEach(function() {
    foo = {
      setBar: function(value) {
        bar = value;
      }
    };
    spyOn(foo, 'setBar');//給foo對象的setBar函數(shù)綁定追蹤
    foo.setBar(123);
    foo.setBar(456, 'another param');
  });
  it("tracks that the spy was called", function() {
    expect(foo.setBar).toHaveBeenCalled();//toHaveBeenCalled用來匹配測試函數(shù)是否被調(diào)用過
  });
  it("tracks all the arguments of its calls", function() {
    expect(foo.setBar).toHaveBeenCalledWith(123);//toHaveBeenCalledWith用來匹配測試函數(shù)被調(diào)用時的參數(shù)列表
    expect(foo.setBar).toHaveBeenCalledWith(456, 'another param');//期望foo.setBar已經(jīng)被調(diào)用過,且傳入?yún)?shù)為[456, 'another param']
  });
  it("stops all execution on a function", function() {
    expect(bar).toBeNull();//用例沒有執(zhí)行foo.setBar,bar為null
  });
});

and.callThrough--spy鏈式調(diào)用and.callThrough后,在獲取spy的同時,調(diào)用實際的函數(shù),看示例:

describe("A spy, when configured to call through", function() {
  var foo, bar, fetchedBar;
  beforeEach(function() {
    foo = {
      setBar: function(value) {
        bar = value;
      },
      getBar: function() {
        return bar;
      }
    };
    spyOn(foo, 'getBar').and.callThrough();//調(diào)用and.callThrough方法
    foo.setBar(123);
    fetchedBar = foo.getBar();//因為and.callThrough,這里執(zhí)行的是foo.getBar方法,而不是spy的方法
  });
  it("tracks that the spy was called", function() {
    expect(foo.getBar).toHaveBeenCalled();
  });
  it("should not effect other functions", function() {
    expect(bar).toEqual(123);
  });
  it("when called returns the requested value", function() {
    expect(fetchedBar).toEqual(123);
  });
});

and.returnValue--spy鏈式調(diào)用and.returnValue 后,任何時候調(diào)用該方法都只會返回指定的值,比如:

describe("A spy, when configured to fake a return value", function() {
  var foo, bar, fetchedBar;
  beforeEach(function() {
    foo = {
      setBar: function(value) {
        bar = value;
      },
      getBar: function() {
        return bar;
      }
    };
    spyOn(foo, "getBar").and.returnValue(745);//指定返回值為745
    foo.setBar(123);
    fetchedBar = foo.getBar();
  });
  it("tracks that the spy was called", function() {
    expect(foo.getBar).toHaveBeenCalled();
  });
  it("should not effect other functions", function() {
    expect(bar).toEqual(123);
  });
  it("when called returns the requested value", function() {
    expect(fetchedBar).toEqual(745);//默認返回指定的returnValue值
  });
});

and.callFake--spy鏈式添加and.callFake相當于用新的方法替換spy的方法,比如:

describe("A spy, when configured with an alternate implementation", function() {
  var foo, bar, fetchedBar;
  beforeEach(function() {
    foo = {
      setBar: function(value) {
        bar = value;
      },
      getBar: function() {
        return bar;
      }
    };
    spyOn(foo, "getBar").and.callFake(function() {//指定callFake方法
      return 1001;
    });
    foo.setBar(123);
    fetchedBar = foo.getBar();
  });
  it("tracks that the spy was called", function() {
    expect(foo.getBar).toHaveBeenCalled();
  });
  it("should not effect other functions", function() {
    expect(bar).toEqual(123);
  });
  it("when called returns the requested value", function() {
    expect(fetchedBar).toEqual(1001);//執(zhí)行callFake方法,返回1001
  });
});

and.throwError--spy鏈式調(diào)用and.callError后,任何時候調(diào)用該方法都會拋出異常錯誤信息:

describe("A spy, when configured to throw an error", function() {
  var foo, bar;
  beforeEach(function() {
    foo = {
      setBar: function(value) {
        bar = value;
      }
    };
    spyOn(foo, "setBar").and.throwError("error");//指定throwError
  });
  it("throws the value", function() {
    expect(function() {
      foo.setBar(123)
    }).toThrowError("error");//拋出錯誤異常
  });
});

and.stub--spy恢復到原始狀態(tài),不執(zhí)行任何操作。直接看下代碼:

describe("A spy", function() {
  var foo, bar = null;
  beforeEach(function() {
    foo = {
      setBar: function(value) {
        bar = value;
      }
    };
    spyOn(foo, 'setBar').and.callThrough();
  });
  it("can call through and then stub in the same spec", function() {
    foo.setBar(123);
    expect(bar).toEqual(123);
    foo.setBar.and.stub();//把foo.setBar設置為原始狀態(tài),and.callThrough無效
    bar = null;
    foo.setBar(123);//執(zhí)行賦值無效
    expect(bar).toBe(null);
  });
});
Spy的其他方法
.calls.any():記錄spy是否被訪問過,如果沒有,則返回false,否則,返回true;
.calls.count():記錄spy被訪問過的次數(shù);
.calls.argsFor(index):返回指定索引的參數(shù);
.calls.allArgs():返回所有函數(shù)調(diào)用的參數(shù)記錄數(shù)組;
.calls.all ():返回所有函數(shù)調(diào)用的上下文、參數(shù)和返回值;
.calls.mostRecent():返回最近一次函數(shù)調(diào)用的上下文、參數(shù)和返回值;
.calls.first():返回第一次函數(shù)調(diào)用的上下文、參數(shù)和返回值;
.calls.reset():清除spy的所有調(diào)用記錄;

虛擬定時器

Jasmine Clock 使用setTimeout 和setInterval 來聲明定時的回調(diào)操作。它使回調(diào)函數(shù)同步執(zhí)行,當Clock的tick時間超過timer的時間,回調(diào)函數(shù)會被觸發(fā)一次。
Jasmine Clock使用jasmine.clock().install 在需要調(diào)用timer函數(shù)的spec和suite中初始化。在執(zhí)行完測試的時候,一定要卸載Clock來還原timer函數(shù)。使用jasmine.clock().tick來推進時間以使注冊的回調(diào)觸發(fā)。
Install--在Spec或者Suite中安裝Jasmine Clock:

beforeEach(function() {
    timerCallback = jasmine.createSpy("timerCallback");
    jasmine.clock().install();
});

Uninstall--保證使用完成后,切記要關閉Jasmine Clock:

afterEach(function() {
    jasmine.clock().uninstall();
});

Tick--使用jasmine.clock().tick來計時,一旦累計的時間達到setTimeout或者setInterval中指定的延時時間,則觸發(fā)回調(diào)函數(shù):

describe("Manually ticking the Jasmine Clock", function() {
  var timerCallback;
  beforeEach(function() {
    timerCallback = jasmine.createSpy("timerCallback");
    jasmine.clock().install();
  });
  afterEach(function() {
    jasmine.clock().uninstall();
  });
  it("causes a timeout to be called synchronously", function() {
    setTimeout(function() {
      timerCallback();
    }, 100);//聲明回調(diào)函數(shù)tick到100ms就觸發(fā)

    expect(timerCallback).not.toHaveBeenCalled();


    jasmine.clock().tick(101);//tick 101 會觸發(fā)上面注冊的setTimeout

    expect(timerCallback).toHaveBeenCalled();
  });
});

Mock Date

describe("Mocking the Date object", function(){
    it("mocks the Date object and sets it to a given time", function() {
      var baseTime = new Date(2016, 1, 27);//new一個指定的時間,沒有參數(shù)則返回當前時間
        jasmine.clock().mockDate(baseTime);//構(gòu)造一個虛擬的當前時間
        jasmine.clock().tick(50);//讓虛擬的當前時間快進50ms
        expect(new Date().getTime()).toEqual(baseTime.getTime() + 50);
    });
  });
});

異步支持

Jasmine支持測試需要執(zhí)行異步操作的specs,調(diào)用beforeEach , it , 和afterEach 的時候,可以帶一個可選的參數(shù)done ,當spec執(zhí)行完成之后需要調(diào)用done 來告訴Jasmine異步操作已經(jīng)完成。
默認Jasmine的超時時間是5s,可以通過全局的jasmine.DEFAULTTIMEOUTINTERVAL 設置。

describe("Asynchronous specs", function() {
  var value;
  beforeEach(function(done) {//傳入done參數(shù)表示要執(zhí)行異步操作
    setTimeout(function() {
      value = 0;
      done();//執(zhí)行done()函數(shù)通知it異步操作已經(jīng)執(zhí)行完畢,必須執(zhí)行
    }, 10000);
  });
 it("should support async execution of test preparation and expectations", function(done) {//傳入done參數(shù)表示要執(zhí)行異步操作
    value++;
    expect(value).toBeGreaterThan(0);
    done();//執(zhí)行done()函數(shù)通知it異步操作已經(jīng)執(zhí)行完畢,必須執(zhí)行
  });
});

Ajax

Jasmine擁有一個用于測試Ajax請求的plug-in:

describe("mocking ajax", function() {
  describe("suite wide usage", function() {
    beforeEach(function() {
      jasmine.Ajax.install();
    });
    afterEach(function() {
      jasmine.Ajax.uninstall();
    });

    it("specifying response when you need it", function() {
      var doneFn = jasmine.createSpy("success");
      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function(args) {
        if (this.readyState == this.DONE) {
          doneFn(this.responseText);
        }
      };

      xhr.open("GET", "/some/cool/url");
      xhr.send();
      expect(jasmine.Ajax.requests.mostRecent().url).toBe('/some/cool/url');
      expect(doneFn).not.toHaveBeenCalled();
      jasmine.Ajax.requests.mostRecent().response({
        "status": 200,
        "contentType": 'text/plain',
        "responseText": 'awesome response'
      });
      expect(doneFn).toHaveBeenCalledWith('awesome response');
    });
    it("allows responses to be setup ahead of time", function () {
      var doneFn = jasmine.createSpy("success");
      jasmine.Ajax.stubRequest('/another/url').andReturn({
        "responseText": 'immediate response'
      });
      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function(args) {
        if (this.readyState == this.DONE) {
          doneFn(this.responseText);
        }
      };

      xhr.open("GET", "/another/url");
      xhr.send();

      expect(doneFn).toHaveBeenCalledWith('immediate response');
    });
  });
  it("allows use in a single spec", function() {
    var doneFn = jasmine.createSpy('success');
    jasmine.Ajax.withMock(function() {
      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function(args) {
        if (this.readyState == this.DONE) {
          doneFn(this.responseText);
        }
      };

      xhr.open("GET", "/some/cool/url");
      xhr.send();

      expect(doneFn).not.toHaveBeenCalled();

      jasmine.Ajax.requests.mostRecent().response({
        "status": 200,
        "responseText": 'in spec response'
      });

      expect(doneFn).toHaveBeenCalledWith('in spec response');
    });
  });
});

KnockoutJS

What should and shouldn’t be tested?

用人不疑,疑人不用。所以你不需要測試Knockout正確的將一個ko.observable()對象顯示在一個前臺data-bind="text: ..."的span對象上,同樣我們也不需要測試ko.computed會在任何依賴發(fā)生變化時運行。那么我們測試什么呢?答案就是邏輯,我們應該更多的關注自己所寫的邏輯代碼,這些才是測試的重點。

一個簡單栗子

var addressBookViewModel = {
  entries : ko.observableArray([]),
  newEntryFirstName : ko.observable(),
  newEntrySurname : ko.observable()
    addNewEntry : function() {
        var newEntry = {
          firstName : this.newEntryFirstName(),
          surname : this.newEntrySurname()
      };
      this.entries.push(newEntry);
      // clear form
      this.newEntryFirstName('');
      this.newEntrySurname('');
  }
};

ko.applyBindings(addressBookViewModel);
分析

可以想象一下這個ViewModel的應用場景,用戶輸入firstname,一個surname并且點擊綁定了'addNewEntry'的按鈕。分析邏輯我們發(fā)現(xiàn),此時一個entry被加入了entries(可能會用在一個table里通過foreach顯示),然后清空。
在開始測試之前我們首先要對代碼進行一些重構(gòu),因為首先這個ViewModel是單例的,并且當前方法驗證點太多我們需要拆分一下。重構(gòu)后的代碼如下:

function AddressBookViewModel() {
  this.entries = ko.observableArray([]);
  this.newEntryFirstName = ko.observable();
  this.newEntrySurname = ko.observable();
  this.addNewEntry = function() {
      addAddressBookEntry(this.newEntryFirstName, this.newEntrySurname, this.entries);
      clearObservables([this.newEntryFirstName, this.newEntrySurname]);
  };
}

function addAddressBookEntry(firstName, surname, list) {
  var newEntry = {
      firstName : ko.toJS(firstName),
      surname : ko.toJS(surname)
  };
  list.push(newEntry);
}

function clearObservables(observables) {
  observables.forEach(function(observable){
      observable(null);
  });
}

var addressBookViewModel = new AddressBookViewModel();
ko.applyBindings(addressBookViewModel);
開始測試
  • addAddressBookEntry
// 'Describe' creates a Jasmine test. A describe block contains assertions, using the 'it' function.
describe('addAddressBookEntry', function(){

  var newEntryFirstName, newEntryLastName, list;

  // 'beforeEach' performs setup before each 'it' test
  beforeEach(function(){
      newEntryFirstName = ko.observable('Peggy');
      newEntryLastName = ko.observable('Hill');
      list = ko.observableArray([]);
  });

  it('Adds an entry to the provided list', function(){
      var initialListLength = list().length;
      addAddressBookEntry(newEntryFirstName, newEntryLastName, list);
      var newListLength = list().length;

      // Jasmine uses the 'expect' function for assertions. Its format is very human-readable.
      // If an expection proves false, it will throw an exception and the assertion will be reported as failed.
      expect(newListLength).toBe(initialListLength + 1);
  });

  it('Adds an entry containing the supplied firstname and surname', function(){
      addAddressBookEntry(newEntryFirstName, newEntryLastName, list);
      var unwrappedList = list();
      var expectedNewEntry = {firstName: 'Peggy', surname: 'Hill'};
      // Jasmine's toContain will, amongst other things, test whether an array contains an object with fields matching a supplied object
      expect(unwrappedList).toContain(expectedNewEntry);
  });

  it('Adds the entry to the end of the list', function(){
      addAddressBookEntry(newEntryFirstName, newEntryLastName, list);
      var unwrappedList = list();
      var lastEntry = unwrappedList[unwrappedList.length - 1];

      // You can have multiple expectations in a Jasmine test
      expect(lastEntry.firstName).toBe('Peggy');
      expect(lastEntry.surname).toBe('Hill');
  });

});

一個普通栗子

function FormatterBinding(formatter) {
  this.update = function update(element, valueAccessor) {
      var newModelValue = ko.unwrap(valueAccessor());
      var formattedText = formatter(newModelValue);
      // let's assume we don't need to support IE8
      element.textContent = formattedText;
  };
}

function formatNumberAsDollars(number) {
  return number.toLocaleString('en-US',{style: 'currency', currency: 'USD', maximumFractionDigits: 2});
}
分析

這個FormatterBinding是用來將數(shù)字格式化為貨幣。對于下面的formatNumberAsDollars我們不需要測試,我們只需關注FormatterBinding。

var mockFormatter, customBinding, mockValueAccessor;
// 'beforeEach' performs setup before each 'it' test
beforeEach(function(){
  mockFormatter = jasmine.createSpy('mockFormatter');
  customBinding = new FormatterBinding(mockFormatter);
  mockElement = document.createElement('p');
  mockValueAccessor = function() {
      return ko.observable('someMockValue');
  }
});

it('Calls the formatter with the value in the valueAccessor', function(){
  // let's assume we've created all our mocks as part of a Jasmine
  // beforeEach block that runs before each set of assertions

  customBinding.update(mockElement, mockValueAccessor);
  expect(mockFormatter).toHaveBeenCalledWith('someMockValue');
});

it('Prints the output of the formatter to the element', function(){
  // Setting up the spy is a little bit awkward
  var mockFunctions = {
      mockFormatter : function() { return 'I am the mockFormatter return value';}
  };
  spyOn(mockFunctons, 'mockFormatter');

  // But everything else is dead easy
  var customBinding = new FormatterBinding(mockFunctions.mockFormatter);
  customBinding.update(mockElement, mockValueAccessor);
  var mockElementContent = mockElement.textContent;
  expect(mockElementContent).toBe('I am the mockFormatter return value');
});

然而并沒有時間再去準備高級栗子了...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

  • Jasmine是什么 Jasmine是一個Javascript BDD測試框架。只要是Javascript能運行的...
    做測試的DanteYu閱讀 1,487評論 0 3
  • Unit Test 單元測試概念(Unit Testing)又稱為模塊測試, 是針對程序模塊(軟件設計的最小單位)...
    點柈閱讀 1,427評論 0 4
  • 寫在前面 本文會介紹一些jasmine的基本概念和語法,并給出簡單易懂的示例。適合初學jasmine者,如果你已經(jīng)...
    Addy_Zhou閱讀 6,645評論 3 6
  • 基本概念 suites suites表示一個測試集,以函數(shù)describe封裝 describe describe...
    只是無情緒閱讀 1,222評論 0 0
  • 以下是我假定那些極少或壓根沒寫單元測試的人準備的,因此,會白話解釋諸多概念性問題,同時會結(jié)合 Jasmine 與之...
    cipchk閱讀 5,017評論 2 4

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