套件:describe 你的測試
describe 函式用於分組相關規格,通常每個測試檔案在頂層都有其中一個。字串參數是用於命名規格集合,並將與規格合併以組成規格的全名。這有助於在較大的套件中找到規格。如果您為它們命名得當,您的規格會以傳統 BDD 樣式讀取為完整句子。
規格
規格是透過呼叫全域 Jasmine 函式 it 定義的,與 describe 相同,這會帶入一個字串和一個函式。這個字串就是規格的標題,函式就是規格或測試。規格包含一個或多個驗證,用於測試程式碼狀態。在 Jasmine 中,驗證是一種斷言,其為真或假。具有所有為真驗證的規格就是通過規格。具有至少一個為假驗證的規格就是失敗規格。
|
describe("A suite", function() {
it("contains spec with an expectation", function() {
expect(true).toBe(true);
});
});
|
這只不過是函式
由於 describe 和 it 區塊是函式,因此它們可以包含用於實作測試的任何可執行碼。JavaScript 範圍規則適用,因此在 describe 中宣告的變數可供套件內的任何 it 區塊使用。
|
describe("A suite is just a function", function() {
let a;
it("and so is a spec", function() {
a = true;
expect(a).toBe(true);
});
});
|
驗證
驗證透過函式 expect 建置,它會帶入一個值,稱為實際值。它與比對函式串連,而比對函式會帶入預期值。
|
describe("The 'toBe' matcher compares with ===", function() {
|
比對函式
每個比對函式會在實際值和預期值之間實作一個布林比較。它負責向 Jasmine 報告驗證為真或假。Jasmine 之後會讓規格通過或失敗。
|
it("and has a positive case", function() {
expect(true).toBe(true);
});
|
任何比對函式都可以透過在呼叫比對函式之前在 expect 呼叫上串連 not 來評估為否定驗證。
|
it("and can have a negative case", function() {
expect(false).not.toBe(true);
});
|
Jasmine 附帶一組豐富的比對函式,您可以在 API 文件 中找到完整清單。對於專案網域要求 Jasmine 未包含的特定斷言時,也有撰寫 自訂比對函式 的功能。
|
|
設定和清除
為了協助測試套件移除重複設定和清除程式碼,Jasmine 提供全域 beforeEach、afterEach、beforeAll 和 afterAll 函式。
|
describe("A suite with some shared setup", function() {
let foo = 0;
|
顧名思義,beforeEach 函式會在呼叫它的 describe 中的每個規格之前呼叫一次
|
beforeEach(function() {
foo += 1;
});
|
而 afterEach 函式會在每個規格之後呼叫一次。
|
afterEach(function() {
foo = 0;
});
|
beforeAll 函式僅在 describe 中的所有規格執行之前呼叫一次
|
beforeAll(function() {
foo = 1;
});
|
而 afterAll 函式會在所有規格結束後呼叫。
|
afterAll(function() {
foo = 0;
});
});
|
beforeAll 和 afterAll 可用於加速設定和清除昂貴的測試套件。
但是,請小心使用 beforeAll 和 afterAll !由於它們不會在規格之間重設,因此很容易意外讓狀態在規格之間洩漏,導致它們錯誤地通過或失敗。
|
|
this 關鍵字
透過 this 關鍵字,可以分享 beforeEach 、it 和 afterEach 之間的變數。每個規格的 beforeEach /it /afterEach 都有 this ,作為設定回空的同一空白物件,供下一個規格的 beforeEach /it /afterEach 使用。
注意:如果您要使用 this 關鍵字分享變數,您必須使用 function 關鍵字,而不是箭頭函式。
|
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);
expect(this.bar).toBe(undefined);
});
});
|
使用 fail 手動使規格失敗
fail 函式會造成規格失敗。它可以將失敗訊息或錯誤物件作為參數。
|
describe("A spec using the fail function", function() {
function foo(x, callBack) {
if (x) {
callBack();
}
};
it("should not call the callBack", function() {
foo(false, function() {
fail("Callback has been called");
});
});
});
|
嵌套 describe 區塊
describe 的呼叫可以嵌套,且規格定義在任何層級。這讓組件能組成函式的樹狀結構。在執行規格之前,Jasmine 會執行樹狀結構並依順序執行每個 beforeEach 函式。在執行規格之後,Jasmine 會在 afterEach 函式中以類似的形式執行。
|
describe("A spec", function() {
let 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() {
let bar;
beforeEach(function() {
bar = 1;
});
it("can reference both scopes as needed", function() {
expect(foo).toEqual(bar);
});
});
});
|
停用組件
組件可以使用 xdescribe 函式停用。這些組件和它們之中的任何規格執行時都會略過,因此其結果會顯示為保留中。
組件也可以使用 fdescribe 函式進行重點關注。這表示只有 fdescribe 組件會執行。
|
xdescribe("A spec", function() {
let foo;
beforeEach(function() {
foo = 0;
foo += 1;
});
it("is just a function, so it can contain any code", function() {
expect(foo).toEqual(1);
});
});
|
保留中規格
保留中規格不會執行,但其名稱會在結果中顯示為 pending 。
|
describe("Pending specs", function() {
|
使用 xit 宣告的任何規格都會標記為保留中。
|
xit("can be declared 'xit'", function() {
expect(true).toBe(false);
});
|
沒有函式本體的任何規格在結果中也會標記為保留中。
|
it("can be declared with 'it' but without a function");
|
如果你在規格本體的任意處呼叫 pending 函式,不論預期結果為何,規格將被標記為保留中。傳入 pending 字串將被視為原因,並在組件完成時顯示。
測試也可以使用 fit 函式進行重點關注。這表示只有 fit 測試會執行。
|
it("can be declared by calling 'pending' in the spec body", function() {
expect(true).toBe(false);
pending('this is why it is pending');
});
});
|
偵測器
Jasmine 具有稱為偵測器的測試替身函式。偵測器可以偽造任何函式,並追蹤其呼叫及其所有引數。偵測器僅存在於定義它的 describe 或 it 區塊中,且在每個規格後都會移除。有針對互動偵測器的特殊比對函式。
|
describe("A spy", function() {
let foo;
let bar = null;
beforeEach(function() {
foo = {
setBar: function(value) {
bar = value;
}
};
|
您可以使用 and 定義偵測器在呼叫時將執行什麼動作。
|
spyOn(foo, 'setBar');
foo.setBar(123);
foo.setBar(456, 'another param');
});
|
如果偵測器被呼叫,toHaveBeenCalled 比對函式會通過。
|
it("tracks that the spy was called", function() {
expect(foo.setBar).toHaveBeenCalled();
});
|
如果偵測器被呼叫指定次數,toHaveBeenCalledTimes 比對函式會通過。
|
it("tracks that the spy was called x times", function() {
expect(foo.setBar).toHaveBeenCalledTimes(2);
});
|
如果引數清單與偵測器的任何已記錄呼叫相符,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() {
expect(bar).toBeNull();
});
|
您可以使用 calls 取得偵測器追蹤其呼叫的所有資料。
|
it("tracks if it was called at all", function() {
foo.setBar();
expect(foo.setBar.calls.any()).toEqual(true);
});
});
|
偵測器:createSpy
如果沒有函式要偵測,jasmine.createSpy 可以建立「空白」偵測器。此偵測器與其他偵測器一樣,會追蹤呼叫、引數等等。但是它背後沒有實作。
|
describe("A spy, when created manually", function() {
let whatAmI;
beforeEach(function() {
whatAmI = jasmine.createSpy('whatAmI');
whatAmI("I", "am", "a", "spy");
});
it("tracks that the spy was called", function() {
expect(whatAmI).toHaveBeenCalled();
});
});
|
偵測器:createSpyObj
若要建立具有多重偵測器之一組,請使用 jasmine.createSpyObj 並傳入一組字串。它會回傳一個物件,其中每一個字串的屬性都是一個偵測器。
|
describe("Multiple spies, when created manually", function() {
let 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();
});
});
|
更精緻的比對
有時您不想使用完全相等來進行比對。Jasmine 提供多項非對稱的相等測試工具。
|
describe("Matching with finesse", function() {
|
jasmine.any 將建構函式或「類別」名稱視為預期值。如果建構函式與實際值的建構函式相符,它會回傳 true 。
|
describe("jasmine.any", function() {
it("matches any value", function() {
expect({}).toEqual(jasmine.any(Object));
expect(12).toEqual(jasmine.any(Number));
});
describe("when used with a spy", function() {
it("is useful for comparing arguments", function() {
const foo = jasmine.createSpy('foo');
foo(12, function() {
return true;
});
expect(foo).toHaveBeenCalledWith(
jasmine.any(Number), jasmine.any(Function)
);
});
});
});
|
jasmine.anything 如果實際值不為 null 或 undefined ,則會回傳 true 。
|
describe("jasmine.anything", function() {
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() {
const foo = jasmine.createSpy('foo');
foo(12, function() {
return false;
});
expect(foo).toHaveBeenCalledWith(12, jasmine.anything());
});
});
});
|
jasmine.objectContaining 適用於預期值僅關心實際值中特定鍵值對的情況。
|
describe("jasmine.objectContaining", function() {
let foo;
beforeEach(function() {
foo = {
a: 1,
b: 2,
bar: "baz"
};
});
it("matches objects with the expect key/value pairs", function() {
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() {
const callback = jasmine.createSpy('callback');
callback({
bar: "baz"
});
expect(callback).toHaveBeenCalledWith(
jasmine.objectContaining({ bar: "baz" })
);
});
});
});
|
jasmine.arrayContaining 適用於預期值僅關心陣列中一些數值的情況。
|
describe("jasmine.arrayContaining", function() {
let 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() {
const 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 適用於您不想在較大物件中完全比對字串,或在偵測器預期值中比對字串中的一部分之情況。
|
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() {
const callback = jasmine.createSpy('callback');
callback('foobarbaz');
expect(callback).toHaveBeenCalledWith(
jasmine.stringMatching('bar')
);
expect(callback).not.toHaveBeenCalledWith(
jasmine.stringMatching(/^bar$/)
);
});
});
});
|
自訂非對稱相等測試工具
當您需要檢查某事物是否符合特定條件,但並非嚴格相等時,也可以指定自訂非對稱相等測試工具,方法是提供具有 asymmetricMatch 函式的物件。
|
describe("custom asymmetry", function() {
const tester = {
asymmetricMatch: function(actual) {
const 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() {
const callback = jasmine.createSpy('callback');
callback('foo,bar,baz');
expect(callback).toHaveBeenCalledWith(tester);
});
});
});
});
|
Jasmine 時間
可以使用 Jasmine 時間 來測試依賴時間的程式碼。
|
describe("Manually ticking the Jasmine Clock", function() {
let timerCallback;
|
會在需要操控時間的 spec 或 suite 中呼叫 jasmine.clock().install 來安裝它。
|
beforeEach(function() {
timerCallback = jasmine.createSpy("timerCallback");
jasmine.clock().install();
});
|
用完時務必解除安裝時鐘,以回復原來的功能。
|
afterEach(function() {
jasmine.clock().uninstall();
});
|
模擬 JavaScript Timeout 函數
你可以讓 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() {
const baseTime = new Date(2013, 9, 23);
|
如果沒有提供 mockDate 的基本時間,它就會使用現在的日期。
|
jasmine.clock().mockDate(baseTime);
jasmine.clock().tick(50);
expect(new Date().getTime()).toEqual(baseTime.getTime() + 50);
});
});
});
|
非同步支援
Jasmine 也支援執行需要測試非同步作業的規範。傳遞給 beforeAll 、afterAll 、beforeEach 、afterEach 以及 it 的函數可以宣告為 async 。
Jasmine 也支援明確回傳承諾或需要回呼的非同步函數。更多資訊請參閱 非同步作業教學。
|
describe("Using async/await", function() {
beforeEach(async function() {
await soon();
value = 0;
});
|
這個規範直到上面呼叫 beforeEach 回傳的承諾完成後才會開始。而且這個規範直到它回傳的承諾完成後才會完成。
|
it("supports async execution of test preparation and expectations",
async function() {
await soon();
value++;
expect(value).toBeGreaterThan(0);
}
);
});
|
Jasmine 預設會針對非同步規範等候 5 秒,才會使它逾時失敗。如果 done 在逾時時間到之前呼叫,目前的規範會被標示為失敗,而且 suite 執行會繼續,就像已呼叫 done 一樣。
如果具體規範應該更快速失敗或需要更多時間,可以用傳遞逾時值給 it 等的方式來調整。
如果整個 suite 應該有不同的逾時時間,可以設定 jasmine.DEFAULT_TIMEOUT_INTERVAL 為全域變數,出現在任何給定的 describe 之外。
|
describe("long asynchronous specs", function() {
beforeEach(async function() {
await somethingSlow();
}, 1000);
it("takes a long time", function() {
await somethingReallySlow();
}, 10000);
afterEach(function() {
await somethingSlow();
}, 1000);
});
function soon() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve();
}, 1);
});
}
});
|