最近的項目在使用AngulaJs,對JS代碼的測試問題就擺在了面前。通過對比我們選擇了 Karma + jasmine ,使用 Jasmine做單元測試 ,Karma 自動化完成,當然瞭如果使用 Karma + jasmine 前提是必須安裝 Nodejs。 安裝好 Nodejs ,使用 npm 安 ...
最近的項目在使用AngulaJs,對JS代碼的測試問題就擺在了面前。通過對比我們選擇了 Karma + jasmine ,使用 Jasmine做單元測試 ,Karma 自動化完成,當然瞭如果使用 Karma + jasmine 前提是必須安裝 Nodejs。
安裝好 Nodejs ,使用 npm 安裝好必要的包,寫了一個測試用例,測試通過,很好很強大。 沒有 Nodejs 環境可以使用 Jasmine 做單元測試嗎?當然可以,我們可以到 官網下一個示例看一看,比較簡單。今天先講一下如果直接使用
jasmine 做單元測試
簡單示例
jasmine 示例下載地址 https://github.com/jasmine/jasmine/releases 選擇最新版本下載下來示例代碼結構如圖
lib 文件夾下麵: boot.js 啟動文件 ,
console.js 輸出輔助文件,
jasmine-html.js 測試頁面 Dom 操作文件,
jasmine.js jasmine核心文件
spec 文件夾 : PlayerSpec.js 單元測試文件
SpecHelper.js jasmine 斷言擴展文件(自定義 Matcher)
src 文件夾 ,下麵是被測試的 js 文件。 SpecRunner.html 為測試結果頁面。
SpecRunner.html 代碼,註意js文件載入順序
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Jasmine Spec Runner v2.5.2</title> <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.5.2/jasmine_favicon.png"> <link rel="stylesheet" href="lib/jasmine-2.5.2/jasmine.css"> <script src="lib/jasmine-2.5.2/jasmine.js"></script> <script src="lib/jasmine-2.5.2/jasmine-html.js"></script> <script src="lib/jasmine-2.5.2/boot.js"></script> <!-- 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>
我們直接運行 SpecRunner.html 測試結果如下:
5個 specs,0個失敗,全部通過。在 PlayerSpec.js 里添加一個Suite,看看報錯是什麼樣的。
describe("error test",function() { it("Here the test does not pass",function() { expect(1).toBe(2); }); })
哈哈,測試未通過,看到沒,這裡顯示了詳細的錯誤信息。
jasmine 語法詳解
首先瞭解幾個概念: Suite 指一個測試集, describe方法標志著一個測試集。
Spec 表示測試用例,jasmine中用方法it來開始 specs。
一個 Suite可以包含多個 Spec,一個 spec 可以包含多個 expections 斷言
示例1
//測試集 開始於調用全局Jasmine函數describe,有兩個參數:一個字元串和一個函數。 //該字元串是specs(測試用例)單元測試的名稱或標題 - 通常是被測試的。 該函數是一個實現單元測試的代碼塊。 describe("A suite", function() { it("contains spec with an expectation", function() { expect(true).toBe(true); }); });
示例2 包含多個斷言
describe("A suite is just a function", function() { var a; it("and so is a spec", function() { a = true; expect(a).toBe(true); }); }); describe("The 'toBe' matcher compares with ===", function() { it("and has a positive case", function() { expect(true).toBe(true); }); it("and can have a negative case", function() { expect(false).not.toBe(true); }); });
示例3 常用語法,describe嵌套
describe("Included matchers:", function() { it("The 'toBe' matcher compares with ===", function() { var a = 12; var b = a; expect(a).toBe(b); expect(a).not.toBe(null); }); describe("The 'toEqual' matcher", function() { it("works for simple literals and variables", function() { var a = 12; expect(a).toEqual(12); }); it("should work for objects", function() { var foo = { a: 12, b: 34 }; var bar = { a: 12, b: 34 }; expect(foo).toEqual(bar); }); }); it("The 'toMatch' matcher is for regular expressions", function() { var message = "foo bar baz"; expect(message).toMatch(/bar/); expect(message).toMatch("bar"); expect(message).not.toMatch(/quux/); }); it("The 'toBeDefined' matcher compares against `undefined`", function() { var a = { foo: "foo" }; //已定義 expect(a.foo).toBeDefined(); expect(a.bar).not.toBeDefined(); }); it("The `toBeUndefined` matcher compares against `undefined`", function() { var a = { foo: "foo" }; //未定義 expect(a.foo).not.toBeUndefined(); expect(a.bar).toBeUndefined(); }); it("The 'toBeNull' matcher compares against null", function() { var a = null; var foo = "foo"; expect(null).toBeNull(); expect(a).toBeNull(); expect(foo).not.toBeNull(); }); it("The 'toBeTruthy' matcher is for boolean casting testing", function() { var a, foo = "foo"; expect(foo).toBeTruthy(); expect(a).not.toBeTruthy(); }); it("The 'toBeFalsy' matcher is for boolean casting testing", function() { var a, foo = "foo"; expect(a).toBeFalsy(); expect(foo).not.toBeFalsy(); }); describe("The 'toContain' matcher", function() { it("works for finding an item in an Array", function() { var a = ["foo", "bar", "baz"]; //包含 expect(a).toContain("bar"); expect(a).not.toContain("quux"); }); it("also works for finding a substring", function() { var a = "foo bar baz"; expect(a).toContain("bar"); expect(a).not.toContain("quux"); }); }); it("The 'toBeLessThan' matcher is for mathematical comparisons", function() { var pi = 3.1415926, e = 2.78; //小於 expect(e).toBeLessThan(pi); expect(pi).not.toBeLessThan(e); }); it("The 'toBeGreaterThan' matcher is for mathematical comparisons", function() { var pi = 3.1415926, e = 2.78; //大於 expect(pi).toBeGreaterThan(e); expect(e).not.toBeGreaterThan(pi); }); it("The 'toBeCloseTo' matcher is for precision math comparison", function() { var pi = 3.1415926, e = 2.78; //臨近 是比較兩個值是否足夠接近(不一定要相等) //源碼:pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2) //即 pi - e 的絕對值 是否 小於 10 的 X(2) 次方 / 2 //以 expect(pi).not.toBeCloseTo(e, 3); 為例,就是 pi 跟 e 的差 絕對值 ,是否小於 1/1000 除以 2 ,即 0.0005 expect(pi).not.toBeCloseTo(e, 2); expect(pi).toBeCloseTo(e, 0); }); it("The 'toThrow' matcher is for testing if a function throws an exception", function() { var foo = function() { return 1 + 2; }; var bar = function() { return a + 1; }; //是否引發異常 expect(foo).not.toThrow(); expect(bar).toThrow(); }); it("The 'toThrowError' matcher is for testing a specific thrown exception", function() { var foo = function() { throw new TypeError("foo bar baz"); }; //是否拋出指定錯誤 expect(foo).toThrowError("foo bar baz"); expect(foo).toThrowError(/bar/); expect(foo).toThrowError(TypeError); expect(foo).toThrowError(TypeError, "foo bar baz"); }); }); //手動製造一個斷言失敗 //fail函數使specs(測試用例)失敗。 它可以將失敗消息或Error對象作為參數。 describe("A spec using the fail function", function() { var foo = function(x, callBack) { if (x) { callBack(); } }; it("should not call the callBack", function() { foo(false, function() { fail("Callback has been called"); }); }); }); //分組相關規則帶 //describe 函數用於對相關specs(測試用例)進行分組。 string參數用於命名specs的集合,並且將與specs連接以構成spec的全名。 //這有助於在 測試集 找到規則。 如果你很好地命名他們,你的規則讀為傳統的BDD風格的完整句子。 describe("A spec", function() { it("is just a function, so it can contain any code", function() { var foo = 0; foo += 1; expect(foo).toEqual(1); }); it("can have more than one expectation", function() { var foo = 0; foo += 1; expect(foo).toEqual(1); expect(true).toEqual(true); }); });
示例4 beforeEach,afterEach,beforeAll和afterAll函數
//顧名思義,beforeEach函數在調用它的describe中的每個 spec 之前調用一次,afterEach函數在每個spec之後調用一次。 //這裡是同一組specs(測試用例)寫得有點不同。 被測變數定義在頂層作用域 - 描述塊和初始化代碼被移入一個beforeEach函數。 //afterEach函數在繼續之前重置變數。 describe("A spec using beforeEach and afterEach", function() { var foo = 0; beforeEach(function() { foo += 1; }); afterEach(function() { foo = 0; }); it("is just a function, so it can contain any code", function() { expect(foo).toEqual(1); }); it("can have more than one expectation", function() { expect(foo).toEqual(1); expect(true).toEqual(true); }); }); //beforeAll函數僅在describe中的所有specs(測試用例)運行之前調用一次,並且afterAll函數在所有specs(測試用例)完成後調用。 //這些功能可用於加快測試集 的昂貴設置和拆卸。 //但是,要小心使用beforeAll和afterAll! 由於它們不在specs(測試用例)之間重置,很容易在specs(測試用例)之間意外泄漏狀態, //使它們錯誤地通過或失敗。 註意跟 beforeEach 的區別, //如果 在第1個 it 里改變了 foo 的值,第2個 it 的值就不是 初始化時的值了 describe("A spec using beforeAll and afterAll", function() { var foo; beforeAll(function() { foo = 1; }); afterAll(function() { foo = 0; }); it("sets the initial value of foo before specs run", function() { expect(foo).toEqual(1); foo += 1; }); it("does not reset foo between specs", function() { expect(foo).toEqual(2); }); });
示例5 this關鍵字共用變數,嵌套describe
//this關鍵字 //另一種在beforeEach,it和afterEach之間共用變數的方法是通過this關鍵字。 //每個spec的beforeEach / it / afterEach都將這個作為同一個空對象,對於下一個spec的beforeEach / it / afterEach設置為空。 describe("A spec", function() { beforeEach(function() { this.foo = 0; }); it("can use the `this` to share state", function() { expect(this.foo).toEqual(0); this.bar = "test pollution?"; }); it("prevents test pollution by having an empty `this` created for the next spec", function() { expect(this.foo).toEqual(0); //註意這裡的區別 undefined expect(this.bar).toBe(undefined); }); }); //嵌套describe , describe 里嵌套 describe //調用describe可以嵌套,在任何級別定義specs(測試用例)。 這允許一個單元測試被組成一個函數樹。 //在執行specs(測試用例)之前,Jasmine沿著樹順序執行每個beforeEach函數。 //在specs(測試用例)執行後,Jasmine類似地遍歷 afterEach 函數。 describe("A spec", function() { var foo; beforeEach(function() { foo = 0; foo += 1; }); afterEach(function() { foo = 0; }); it("is just a function, so it can contain any code", function() { expect(foo).toEqual(1); }); it("can have more than one expectation", function() { expect(foo).toEqual(1); expect(true).toEqual(true); }); describe("nested inside a second describe", function() { var bar; beforeEach(function() { bar = 1; }); it("can reference both scopes as needed", function() { expect(foo).toEqual(bar); }); }); });
示例6 Pending 待定規則
//待定規則 //待處理的規則不會運行,但它們的名稱將在結果中顯示為待處理。 describe("Pending specs", function() { //任何用xit聲明的spec都被標記為pending。 xit("can be declared 'xit'", function() { expect(true).toBe(false); }); //在沒有函數體的情況下聲明的任何specs(測試用例)也將在結果中被標記為待處理 it("can be declared with 'it' but without a function"); //pending() 如果你在specs(測試用例)體中任何地方調用該函數,無論預期如何,specs(測試用例)將被標記為待定。 //pending()函數接受一個字元串參數,該參數會在結果集中顯示在 PENDING WITH MESSAGE:之後,作為為何被Pending的原因。 it("can be declared by calling 'pending' in the spec body", function() { expect(true).toBe(false); pending('this is why it is pending'); }); });
示例7 Spies 對象監控
// Spies //Jasmine有 spies(監控) 雙重測試功能。 spy 可以存根任何函數並跟蹤對它和所有參數的調用。 //spy 只存在於描述或其定義的塊中,並且將在每個specs(測試用例)之後刪除。 有特殊的匹配器與 spies 交互。 //Jasmine 2.0的語法已更改。 describe("A spy", function() { var foo, bar = null; beforeEach(function() { foo = { setBar: function(value) { bar = value; } }; spyOn(foo, 'setBar'); //spyOn(foo, 'setBar').and.callThrough(); foo.setBar(123); foo.setBar(456, 'another param'); }); //如果調用 Spies ,toHaveBeenCalled匹配器將返回true。 //是否被調用 it("tracks that the spy was called", function() { expect(foo.setBar).toHaveBeenCalled(); }); //如果 Spies 被調用了指定的次數,toHaveBeenCalledTimes匹配器將通過。 it("tracks that the spy was called x times", function() { expect(foo.setBar).toHaveBeenCalledTimes(2); }); //如果參數列表匹配任何記錄的調用到 Spies ,toHaveBeenCalledWith匹配器將返回true。 it("tracks all the arguments of its calls", function() { expect(foo.setBar).toHaveBeenCalledWith(123); expect(foo.setBar).toHaveBeenCalledWith(456, 'another param'); }); it("stops all execution on a function", function() { //有沒有感到奇怪,beforeEach 里調用了 foo.setBar(),這裡為什麼 bar 的值為 null ?? //原因是 spyOn(foo, 'setBar'); 並不會去調用 真實的 foo.setBar()函數,只是調用了 Jasmine 保存的這個函數的 存根,不會影響到實際的值 //如果這樣寫 spyOn(foo, 'setBar').and.callThrough(); 就會調用真實的 foo.setBar()函數了,bar的值也會跟隨改變 expect(bar).toBeNull(); }); }); // Spies :and.callThrough //通過使用and.callThrough鏈接 Spies , Spies 仍然會跟蹤對它的所有調用,但此外它將委派給實際的實現。 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(); foo.setBar(123); fetchedBar = foo.getBar(); }); it("tracks that the spy was called", function() { expect(foo.getBar).toHaveBeenCalled(); }); it("should not affect other functions", function() { expect(bar).toEqual(123); }); it("when called returns the requested value", function() { //這裡 fetchedBar 有值 //這就是 spyOn(foo, 'getBar').and.callThrough() 跟 spyOn(foo, 'getBar') 的區別 expect(fetchedBar).toEqual(123); }); }); // Spies :and.returnValue //通過使用and.returnValue鏈接 Spies ,所有對函數的調用都將返回特定的值。 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); foo.setBar(123); //所有調用 foo.getBar() 函數都返回 745 fetchedBar = foo.getBar(); }); it("tracks that the spy was called", function() { expect(foo.getBar).toHaveBeenCalled(); }); it("should not affect other functions", function() { expect(bar).toEqual(123); }); it("when called returns the requested value", function() { expect(fetchedBar).toEqual(745); }); }); // specs :and.returnValues //通過使用and.returnValues鏈接 specs ,所有對函數的調用將按順序返回特定的值, //直到它到達返回值列表的結尾,此時它將返回未定義的所有後續調用。 describe("A spy, when configured to fake a series of return values", function() { var foo, bar; beforeEach(function() { foo = { setBar: function(value) { bar = value; }, getBar: function() { return bar; } }; spyOn(foo, "getBar").and.returnValues("fetched first", "fetched second"); foo.setBar(123); }); it("tracks that the spy was called", function() { //返回調用次數 對應的 參數數組 下標的值 foo.getBar(123); expect(foo.getBar).toHaveBeenCalled(); }); it("should not affect other functions", function() { //不要迷惑了,賦值是在 beforeEach 里做的,不是 foo.getBar(123); expect(bar).toEqual(123); }); it("when called multiple times returns the requested values in order", function() { //返回調用次數 對應的 參數數組 下標的值 expect(foo.getBar()).toEqual("fetched first"); expect(foo.getBar()).toEqual("fetched second"); expect(foo.getBar()).toBeUndefined(); }); }); // specs :and.callFake //通過使用and.callFake鏈接 specs ,所有對 specs 的調用都將委派給提供的函數。 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(arguments, can, be, received) { return 1001; }); foo.setBar(123); fetchedBar = foo.getBar(); }); it("tracks that the spy was called", function() { expect(foo.getBar).toHaveBeenCalled(); }); it("should not affect other functions", function() { expect(bar).toEqual(123); }); it("when called returns the requested value", function() { expect(fetchedBar).toEqual(1001); }); }); // specs :and.throwError //通過使用and.throwError鏈接 specs ,所有對 specs 的調用都將拋出指定的值作為錯誤。 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("quux"); }); it("throws the value", function() { expect(function() { foo.setBar(123) }).toThrowError("quux"); }); }); // specs :and.stub //當調用策略用於 specs 時,可以隨時使用and.stub返回原始的存根行為。 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(); bar = null; foo.setBar(123); //返回原始的存根 expect(bar).toBe(null); }); });
在上面這段代碼里 ,要註意 Spies :and.callThrough 的用法 註意代碼 spyOn(foo, 'getBar').and.callThrough(); 跟 spyOn(foo, 'getBar'); 的區別 spyOn(foo, 'getBar').and.callThrough() 會調用實例方法
產生實際的影響,而 spyOn(foo, 'getBar'); 只是調用了 Jasmine 保存的這個函數的 存根,不會影響到實際的值 ,如果沒看明白請仔細看代碼上我添加的註釋。
示例8 其他屬性
describe("A spy", function() { var foo, bar = null; //每個對 specs 的調用都會被跟蹤併在calls屬性上公開 beforeEach(function() { foo = { setBar: function(value) { bar = value; } }; spyOn(foo, 'setBar'); }); //.calls.any():如果spy沒有被調用,則返回false,如果至少有一個調用發生,則返回true it("tracks if it was called at all", function() { expect(foo.setBar.calls.any()).toEqual(false); foo.setBar(); expect(foo.setBar.calls.any()).toEqual(true); }); //.calls.count():返回調用 specs 的次數 it("tracks the number of times it was called", function() { expect(foo.setBar.calls.count()).toEqual(0); foo.setBar(); foo.setBar(); expect(foo.setBar.calls.count()).toEqual(2); }); //.calls.argsFor(index):返回傳遞給調用號索引的參數 it("tracks the arguments of each call", function() { foo.setBar(123); foo.setBar(456, "baz"); expect(foo.setBar.calls.argsFor(0)).toEqual([123]); expect(foo.setBar.calls.argsFor(1)).toEqual([456, "baz"]); }); //.calls.allArgs():返回所有調用的參數 it("tracks the arguments of all calls", function() { foo.setBar(123); foo.setBar(456, "baz"); expect(foo.setBar.calls.allArgs()).toEqual([[123],[456, "baz"]]); }); //.calls.all():返回上下文(this)和傳遞所有調用的參數 it("can provide the context and arguments to all calls", function() { foo.setBar(123); expect(foo.setBar.calls.all()).toEqual([{object: foo, args: [123], returnValue: undefined}]); }); //.calls.mostRecent():返回上一次調用的上下文(this)和參數 it("has a shortcut to the most recent call", function() { foo.setBar(123); foo.setBar(456, "baz"); expect(foo.setBar.calls.mostRecent()).toEqual({object: foo, args: [456, "baz"], returnValue: undefined}); }); //.calls.first():返回上下文(this)和第一次調用的參數 it("has a shortcut to the first call", function() { foo.setBar(123); foo.setBar(456, "baz"); expect(foo.setBar.calls.first()).toEqual({object: foo, args: [123], returnValue: undefined}); }); //當檢查來自all(),mostRecent()和first()的返回時,當調用 specs 時,object屬性被設置為this的值。 it("tracks the context", function() { var spy = jasmine.createSpy('spy'); var baz = { fn: spy }; var quux = { fn: spy }; baz.fn(123); quux.fn(456); //.object 返回的this ,即調用對象 expect(spy.calls.first().object).toBe(baz); expect(spy.calls.mostRecent().object).toBe(quux); }); //.calls.reset():清除 specs 的所有跟蹤 it("can be reset", function() { foo.setBar(123); foo.setBar(456, "baz"); expect(foo.setBar.calls.any()).toBe(true); foo.setBar.calls.reset(); expect(foo.setBar.calls.any()).toBe(false); }); }); // specs :createSpy //當沒有一個函數來監視,jasmine.createSpy可以創建一個“裸” specs 。 //這個 specs 作為任何其他 specs - 跟蹤調用,參數等,但其沒有實現。 specs 是JavaScript對象,可以這樣使用。 describe("A spy, when created manually", function() { var whatAmI; beforeEach(function() { whatAmI = jasmine.createSpy('whatAmI'); whatAmI("I", "am", "a", "spy"); }); it("is named, which helps in error reporting", function() { expect(whatAmI.and.identity()).toEqual('whatAmI'); }); it("tracks that the spy was called", function() { expect(whatAmI).toHaveBeenCalled(); }); it("tracks its number of calls", function() { expect(whatAmI.calls.count()).toEqual(1); }); it("tracks all the arguments of its calls", function() { expect(whatAmI).toHaveBeenCalledWith("I", "am", "a", "spy"); }); it("allows access to the most recent call", function() { expect(whatAmI.calls.mostRecent().args[0]).toEqual("I"); }); }); // specs :createSpyObj //為了創建一個有多個 specs 的模擬,使用jasmine.createSpyObj並傳遞一個字元串數組。 它返回一個對象,它具有屬於 specs 的每個字元串的屬性。 describe("Multiple spies, when created manually", function() { var tape; beforeEach(function() { tape = jasmine.createSpyObj('tape', ['play', 'pause', 'stop', 'rewind']); tape.play(); tape.pause(); tape.rewind(0); }); it("creates spies for each requested function", function() { expect(tape.play).toBeDefined(); expect(tape.pause).toBeDefined(); expect(tape.stop).toBeDefined(); expect(tape.rewind).toBeDefined(); }); it("tracks that the spies were called", function() { expect(tape.play).toHaveBeenCalled(); expect(tape.pause).toHaveBeenCalled(); expect(tape.rewind).toHaveBeenCalled(); expect(tape.stop).not.toHaveBeenCalled(); }); it("tracks all the arguments of its calls", function() { expect(tape.rewind).toHaveBeenCalledWith(0); }); }); //匹配所有與jasmine.any describe("jasmine.anything", function() { //如果實際值不為null或未定義,jasmine.anything返回true。 it("matches anything", function() { expect(1).toEqual(jasmine.anything()); }); describe("when used with a spy", function() { it("is useful when the argument can be ignored", function() { var foo = jasmine.createSpy('foo'); foo(12, function() { return false; }); expect(foo).toHaveBeenCalledWith(12, jasmine.anything()); }); }); }); //與jasmine.objectContaining的部分匹配 //jasmine.objectContaining是用於期望在實際中只關心某些鍵/值對的時候。 describe("jasmine.objectContaining", function() { var foo; beforeEach(function() { foo = { a: 1, b: 2, bar: "baz" }; }); it("matches objects with the expect key/value pairs", function() { //只比對bar expect(foo).toEqual(jasmine.objectContaining({ bar: "baz" })); expect(foo).not.toEqual(jasmine.objectContaining({ c: 37 })); }); describe("when used with a spy", function() { it("is useful for comparing arguments", function() { var callback = jasmine.createSpy('callback'); callback({ bar: "baz" }); expect(callback).toHaveBeenCalledWith(jasmine.objectContaining({ bar: "baz" })); expect(callback).not.toHaveBeenCalledWith(jasmine.objectContaining({ c: 37 })); }); }); }); //部分數組與jasmine.arrayContaining相匹配 //jasmine.arrayContaining用於那些期望只關心數組中的某些值的時候。 describe("jasmine.arrayContaining", function() { var foo; beforeEach(function() { foo = [1, 2, 3, 4]; }); it("matches arrays with some of the values", function() { expect(foo).toEqual(jasmine.arrayContaining([3, 1])); expect(foo).not.toEqual(jasmine.arrayContaining([6])); }); describe("when used with a spy", function() { it("is useful when comparing arguments", function() { var callback = jasmine.createSpy('callback'); callback([1, 2, 3, 4]); expect(callback).toHaveBeenCalledWith(jasmine.arrayContaining([4, 2, 3])); expect(callback).not.toHaveBeenCalledWith(jasmine.arrayContaining([5, 2])); }); }); }); //字元串與jasmine.stringMatching匹配 //jasmine.stringMatching用於當你不想完全匹配較大對象中的字元串時,或者匹配 specs 預期中的字元串的一部分。 describe('jasmine.stringMatching', function() { it("matches as a regexp", function() { expect({foo: 'bar'}).toEqual({foo: jasmine.stringMatching(/^bar$/)}); expect({foo: 'foobarbaz'}).toEqual({foo: jasmine.stringMatching('bar')}); }); describe("when used with a spy", function() { it("is useful for comparing arguments", function() { var callback = jasmine.createSpy('callback'); callback('foobarbaz'); expect(callback).toHaveBeenCalledWith(jasmine.stringMatching('bar')); expect(callback).not.toHaveBeenCalledWith(jasmine.stringMatching(/^bar$/)); }); }); }); //定製不對稱等式測試器 //當您需要檢查某個滿足特定標準的條件,而不是嚴格相等時,您還可以通過提供具有asymmetricMatch函數的對象來指定自定義非對稱等式測試器。 describe("custom asymmetry", function() { var tester = { asymmetricMatch: function(actual) { var secondValue = actual.split(',')[1]; return secondValue === 'bar'; } }; it("dives in deep", function() { expect("foo,bar,baz,quux").toEqual(tester); }); describe("when used with a spy", function() { it("is useful for comparing arguments", function() { var callback = jasmine.createSpy('callback'); callback('foo,bar,baz'); expect(callback).toHaveBeenCalledWith(tester); }); }); });
示例 9 Jasmine 時鐘
//Jasmine 時鐘 //Jasmine 2.0的此語法已更改。 Jasmine時鐘可用於測試時間相關代碼。 describe("Manually ticking the Jasmine Clock", function() { var timerCallback; //它安裝調用了 jasmine.clock()。安裝在需要操縱時間的spec或suite。 beforeEach(function() { timerCallback = jasmine.createSpy("timerCallback"); jasmine.clock().install(); }); //完成恢複原始功能後,請務必卸載時鐘。 afterEach(function() { jasmine.clock().uninstall(); }); //模擬JavaScript超時函數 //您可以使setTimeout或setInterval同步執行已註冊的函數,只有當時鐘在時間上向前跳過時。 //要執行註冊的函數,通過jasmine.clock()。tick函數延時時間,該函數 參數為 毫秒。 it("causes a timeout to be called synchronously", function() { setTimeout(function() { timerCallback(); }, 100); expect(timerCallback).not.toHaveBeenCalled(); jasmine.clock().tick(101); expect(timerCallback).toHaveBeenCalled(); }); it("causes an interval to be called synchronously", function() { setInterval(function() { timerCallback(); }, 100); expect(timerCallback).not.toHaveBeenCalled(); jasmine.clock().tick(101); expect(timerCallback.calls.count()).toEqual(1); jasmine.clock().tick(50); expect(timerCallback.calls.count()).toEqual(1); jasmine.clock().tick(50); expect(timerCallback.calls.count()).toEqual(2); }); //模擬日期 //Jasmine時鐘也可以用來模擬當前日期。 describe("Mocking the Date object", function(){ it("mocks the Date object and sets it to a given time", function() { var baseTime = new Date(2013, 9, 23); //如果你沒有為mockDate提供基準時間,它將使用當前日期。 jasmine.clock().mockDate(baseTime); jasmine.clock().tick(50); expect(new Date().getTime()).toEqual(baseTime.getTime() + 50); }); }); });
示例 10 非同步支持
//非同步支持 //Jasmine 2.0的此語法已更改。 Jasmine還支持運行需要測試非同步操作的specs(測試用例)。 describe("Asynchronous specs", function() { var value; //調用beforeAll,afterAll,beforeEach,afterEach和它可以接受一個可選的單個參數,當非同步工作完成時,應該調用。 beforeEach(function(done) { setTimeout(function() { value = 0; done(); }, 1); }); //在done函數在調用之前,這個specs(測試用例)不會開始。 //這個specs(測試用例)將會等待 beforeEach 調用 done() 後執行。 it("should support async execution of test preparation and expectations", function(done) { value++; expect(value).toBeGreaterThan(0); expect(value).toBe(1);//所以這裡value 的值為1 done(); }); //預設情況下,jasmine將等待5秒鐘,非同步specs(測試用例)在導致超時失敗之前完成。 //如果超時在調用done之前超時,則當前specs(測試用例)將被標記為失敗,並且單元測試執行將繼續,如同調用完成。 //如果特定規則應該更快失敗或需要更多時間,可以通過向其傳遞超時值等來調整。 //如果整個單元測試應該有不同的超時,則可以在任何給定描述之外全局設置jasmine.DEFAULT_TIMEOUT_INTERVAL。 describe("long asynchronous specs", function() { beforeEach(function(done) { done(); }, 1000); it("takes a long time", function(done) { setTimeout(function() { done(); }, 9000); }, 10000); afterEach(function(done) { done(); }, 1000); }); //done.fail函數使specs(測試用例)失敗,並指示它已完成 describe("A spec using done.fail", function() { var foo = function(x, callBack1, callBack2) { if (x) { setTimeout(callBack1, 0); } else { setTimeout(callBack2, 0); } }; it("should not call the second callBack", function(done) { foo(true, done, function() { done.fail("Second callback has been called"); } ); }); }); });
示例11 自定義matcher
//通常,項目將要封裝用於跨多個規範的自定義匹配代碼。 下麵是如何創建一個Jasmine相容的自定義匹配器。 //在其根部的自定義匹配器是比較函數,其獲取實際值和期望值。 //這個工廠被傳遞給Jasmine,理想的情況是調用beforeEach,並且在一個給定的調用中描述的所有規範的範圍內。 //定製匹配器在規格之間拆分。 工廠的名稱將是在期望的調用的返回值上暴露的匹配器的名稱。 var customMatchers = { //自定義匹配器工廠傳遞兩個參數:util,它有一組用於匹配器使用的效用函數(見:matchersUtil.js用於當前列表)和customEqualityTesters, //如果util.equals被調用,則需要傳遞。 當調用匹配器時,可以使用這些參數。 toBeGoofy: function (util, customEqualityTesters) { //工廠方法應該返回一個含有比較函數的對象,該函數將被調用以檢查期望值。 return { //比較函數第一個參數為實際值 ,第二個參數傳遞給匹配器本身的值(如果有的話)。 compare: function (actual, expected) { //toBeGoofy接受一個可選的期望參數,所以如果不傳遞,在這裡定義。 if (expected === undefined) { expected = ''; } var result = {}; if (result.pass) { //如果未定義,期望將嘗試為匹配器創建失敗消息。 但是,如果返回值具有message屬性,它將用於失敗的期望。 result.message = "Expected " + actual + " not to be quite so goofy"; } else { //匹配成功,所以自定義失敗消息應該出現在負期望的情況下 - 當期望與.not一起使用時。 result.message = "Expected " + actual + " to be goofy, but it was not very goofy"; } return result; } }; } }; //調用代碼 describe("Custom matcher: 'toBeGoofy'", function() { beforeEach(function() { jasmine.addMatchers(customMatchers); }); it("is available on an expectation", function () { expect({ hyuk: 'gawrsh' }).toBeGoofy(); }); it("can take an 'expected' parameter", function () { expect({ hyuk: 'gawrsh is fun' }).toBeGoofy('is fun'); }); it("can be negated", function () { expect({ hyuk: 'this is fun' }).not.toBeGoofy(); }); });
看完上面的示例應該在項目中應用沒有什麼問題了。