Jasmine 是甚麼 ?
Jasmine 是一個 JavaScript 的測試框架,
提供了一系列的 API 用於執行單元測試,
在 Angular 也常常使用 Jasmine 來進行測試.
安裝 Jasmine
Jasmine 已經預先安裝在 Angular CLI 中,
只需要使用 ng test
執行測試就可以了,
如果要在其他地方使用的話,
可以使用 npm
或 yarn
來安裝 Jasmine
創建測試套件
一開始我們需要創建一個 測試套件,
並在其中定義測試用例
可以使用 describe
函數來創建測試套件
describe('登入頁功能', () => {
.
.
.
});
創建測試用例
在測試套件中,
需要使用 it
函數來創建測試用例,
it
函數需要兩個參數,
描述 和 函數,
其中包含實際的 測試邏輯
describe('登入頁功能', () => {
it('應該檢核帳密輸入', () => {
expect something...
});
});
撰寫斷言
在測試用例中需要撰寫 斷言,
以判斷測試結果是否符合預期,
Jasmine 提供了許多內置的 匹配器 用於撰寫斷言
describe("登入頁功能", () => {
it("應該檢查帳密輸入", () => {
const something = "something";
expect(something).toEqual("something");
});
});
運行測試
接著運行測試套件,
以檢查程式及邏輯是否正常運行,
在 Angular CLI 中,
使用 ng test
來執行單元測試
Jasmine 撰寫規範
編寫 可讀性高 的測試用例描述
將測試用例 分組, 提高可維護性
使用
beforeEach
和afterEach
, 避免執行重複的操作避免在測試用例之間共享狀態, 每個單元測試應該要獨立運行
Suit & Spec
單元測試應該要這樣寫:
describe('登入頁功能', () => {
it('應該檢核帳密輸入', () => {
expect something...
});
it('若登入成功,應該跳轉到系統頁', () => {
expect something...
});
});
describe 巢狀
可以將 describe
分組:
describe('登入頁功能', () => {
it('應該檢核帳密輸入', () => {
expect something...
});
describe('登入成功後', () => {
it('應該跳轉到系統頁', () => {
expect something...
});
});
});
focus & skip
要執行特定的 describe
與 it
可以使用 f
(focus):
describe('登入頁功能', () => { // 不執行
it('應該檢核帳密輸入', () => { // 不執行
expect something...
});
});
fdescribe('登入成功後', () => { // 執行
it('應儲存使用者資料', () => { // 不執行
expect something...
});
fit('應該跳轉到系統頁', () => { // 執行
expect something...
});
});
要跳過特定的 describe
與 it
可以使用 x
:
xdescribe('登入頁功能', () => { // 不執行
it('應該檢核帳密輸入', () => { // 不執行
expect something...
});
});
describe('登入成功後', () => { // 執行
xit('應儲存使用者資料', () => { // 不執行
expect something...
});
it('應該跳轉到系統頁', () => { // 執行
expect something...
});
});
如果 xdescribe
遇到 fit
的時候:
xdescribe('登入頁功能', () => { // 不執行
it('應該檢核帳密輸入', () => { // 不執行
expect something...
});
});
xdescribe('登入成功後', () => { // 因為 fit 的關係, 所也會執行
fit('應儲存使用者資料', () => { // 還是會執行!
expect something...
});
it('應該跳轉到系統頁', () => { // 執行
expect something...
});
});
Setup & Teardown
在每個測試用例之前或之後執行某些操作
beforeEach
describe
執行後, 每個 it
被執行前, 執行 beforeEach
:
describe("示範", () => {
beforeEach(() => {
// do something ...
});
});
afterEach
每個 it
執行後, 執行 afterEach
:
describe("示範", () => {
afterEach(() => {
// do something ...
});
});
beforeAll
describe
執行後, it
被執行前, 調用 beforeEach
(只執行一次):
describe("示範", () => {
beforeAll(() => {
// do something ...
});
});
afterAll
全部 it
執行後, 調用 afterAll
:
describe("示範", () => {
afterAll(() => {
// do something ...
});
});
Expect
expect
用於撰寫斷言
expect
expect
可以用來預期參數或函式執行後為某數值或某行為:
expect(1).toBe(1);
expect(1).not.toBe(2);
expectAsync
expectAsync
用於預期異步函式執行後為某數值或某行為:
expectAsync(1).toBeResolvedTo(1);
expectAsync(1).not.toBeResolvedTo(2);
Matchers
讓 expect
進行比較和匹配, 檢查 實際結果 與 期望結果, 進行判斷
not
not
用於反轉匹配:
expect(1).not.toBe(2);
nothing
nothing
用於檢查函式沒有回傳值:
expect().nothing();
toBe
toBe
用於檢查兩個變數是否相等:
expect(1).toBe(1);
toEqual
toEqual
用於檢查兩個變數是否相等, 為深度比較:
expect(1).toEqual(1);
toBeCloseTo
toBeCloseTo
用於檢查兩個變數是否相等, 但是會忽略浮點數的誤差:
expect(0.1 + 0.2).not.toBe(0.3); // 0.30000000000000004
expect(0.1 + 0.2).toBeCloseTo(0.3); // 0.3
toBeDefined
toBeDefined
用於檢查變數是否已定義:
expect(1).toBeDefined();
toBeUndefined
toBeUndefined
用於檢查變數是否未定義:
expect(undefined).toBeUndefined();
toBeNaN
toBeNaN
用於檢查變數是否為 NaN:
expect(NaN).toBeNaN();
toBeNull
toBeNull
用於檢查變數是否為 null:
expect(null).toBeNull();
toBeFalse
toBeFalse
用於檢查變數是否為 false:
expect(false).toBeFalse();
toBeTrue
toBeTrue
用於檢查變數是否為 true:
expect(true).toBeTrue();
toBeFalsy
toBeFalsy
用於檢查變數是否為 falsy:
expect(false).toBeFalsy();
toBeTruthy
toBeTruthy
用於檢查變數是否為 truthy:
expect(1).toBeTruthy();
toBeGreaterThan
toBeGreaterThan
用於檢查變數是否大於某數值:
expect(2).toBeGreaterThan(1);
toBeGreaterThanOrEqual
toBeGreaterThanOrEqual
用於檢查變數是否大於等於某數值:
expect(2).toBeGreaterThanOrEqual(1);
expect(2).toBeGreaterThanOrEqual(2);
toBeLessThan
toBeLessThan
用於檢查變數是否小於某數值:
expect(1).toBeLessThan(2);
toBeLessThanOrEqual
toBeLessThanOrEqual
用於檢查變數是否小於等於某數值:
expect(1).toBeLessThanOrEqual(2);
expect(1).toBeLessThanOrEqual(1);
toBeInstanceOf
toBeInstanceOf
用於檢查變數是否為某類別的實例:
class Foo {}
expect(new Foo()).toBeInstanceOf(Foo);
toBeNegativeInfinity
toBeNegativeInfinity
用於檢查變數是否為負無窮大:
expect(Number.NEGATIVE_INFINITY).toBeNegativeInfinity();
toBePositiveInfinity
toBePositiveInfinity
用於檢查變數是否為正無窮大:
expect(Number.POSITIVE_INFINITY).toBePositiveInfinity();
toBeContain
toBeContain
用於檢查陣列是否包含某元素:
expect([1, 2, 3]).toContain(1);
expect([{ v: "a" }, { v: "b" }]).toContain({ v: "a" });
expect("abc").toContain("a");
toHaveBeenCalled
toHaveBeenCalled
用於檢查 spy 方法是否被調用過:
const obj = {
getData: (key) => "aaaa",
};
spyOn(obj, "getData");
expect(obj.getData).not.toHaveBeenCalled();
obj.getData("b");
expect(obj.getData).toHaveBeenCalled();
toHaveBeenCalledTimes
toHaveBeenCalledTimes
用於檢查 spy 方法被調用的次數:
const obj = {
getData: (key) => "aaaa",
};
spyOn(obj, "getData");
expect(obj.getData).not.toHaveBeenCalledTimes(1);
obj.getData("b");
expect(obj.getData).toHaveBeenCalledTimes(1);
toHaveBeenCalledWith
toHaveBeenCalledWith
用於檢查 spy 方法被調用時的參數:
const obj = {
getData: (key) => "aaaa",
};
spyOn(obj, "getData");
expect(obj.getData).not.toHaveBeenCalledWith("b");
obj.getData("b");
expect(obj.getData).toHaveBeenCalledWith("b");
toHaveBeenCalledBefore
toHaveBeenCalledBefore
用於檢查 spy 方法是否在另一個 spy 方法之前被調用:
const obj = {
getData: (key) => "aaaa",
};
const obj2 = {
getData: (key) => "bbbb",
};
spyOn(obj, "getData");
spyOn(obj2, "getData");
obj.getData("b");
obj2.getData("b");
expect(obj.getData).toHaveBeenCalledBefore(obj2.getData);
toHaveBeenCalledAfter
toHaveBeenCalledAfter
用於檢查 spy 方法是否在另一個 spy 方法之後被調用:
const obj = {
getData: (key) => "aaaa",
};
const obj2 = {
getData: (key) => "bbbb",
};
spyOn(obj, "getData");
spyOn(obj2, "getData");
obj.getData("b");
obj2.getData("b");
expect(obj2.getData).toHaveBeenCalledAfter(obj.getData);
toHaveClass
toHaveClass
用於檢查元素是否有某個 class:
const el = document.createElement("div");
el.classList.add("foo");
expect(el).toHaveClass("foo");
toHaveCssStyle
toHaveCssStyle
用於檢查元素是否有某個 css style:
const el = document.createElement("div");
el.style.color = "red";
expect(el).toHaveCssStyle({ color: "red" });
toMatch
toMatch
用於檢查變數是否符合某正規表達式:
expect("abc").toMatch(/abc/);
toMatchObject
toMatchObject
用於檢查變數是否符合某物件:
expect({ a: 1, b: 2 }).toMatchObject({ a: 1 });
toThrow
toThrow
用於檢查函式是否拋出錯誤:
expect(() => {
throw new Error("error");
}).toThrow("error");
toThrowError
toThrowError
用於檢查函式是否拋出 特定錯誤類別 或是 特定錯誤訊息:
const a = () => {
throw new TypeError("foo bar baz");
};
expect(a).toThrowError(TypeError);
expect(a).toThrowError("foo bar baz");
toThrowMatching
toThrowMatching
用於檢查函式是否拋出指定錯誤值:
expect(() => {
throw new TypeError("foo bar baz");
}).toThrowMatching((error) => error.message === "foo bar baz");
withContext
withContext
用於檢查函式是否拋出錯誤時, 有指定的上下文:
expect(() => {
throw new TypeError("foo bar baz");
}).withContext("error");
jasmine.any
jasmine.any
用於檢查變數是否為某類別的實例:
class Foo {}
expect(new Foo()).toEqual(jasmine.any(Foo));
jasmine.anything
jasmine.anything
用於檢查變數是否為 undefined
或 null
以外的任何值:
expect(1).toEqual(jasmine.anything());
expect(null).not.toEqual(jasmine.anything());
expect(undefined).not.toEqual(jasmine.anything());
jasmine.truthy
jasmine.truthy
用於檢查變數是否為 truthy:
expect(1).toEqual(jasmine.truthy());
jasmine.falsy
jasmine.falsy
用於檢查變數是否為 falsy:
expect(0).toEqual(jasmine.falsy());
jasmine.empty
jasmine.empty
用於檢查變數是否為空:
expect([]).toEqual(jasmine.empty());
jasmine.notEmpty
jasmine.notEmpty
用於檢查變數是否不為空:
expect([1]).toEqual(jasmine.notEmpty());
jasmine.arrayContaining
jasmine.arrayContaining
用於檢查陣列是否包含某元素:
expect([1, 2, 3]).toEqual(jasmine.arrayContaining([1]));
jasmine.arrayWithExactContents
jasmine.arrayWithExactContents
用於檢查陣列是否包含某元素, 且元素順序也要相同:
expect([1, 2, 3]).toEqual(jasmine.arrayWithExactContents([1, 2, 3]));
jasmine.mapContaining
jasmine.mapContaining
用於檢查物件是否包含在 Map
裡的 Key
& value
:
expect(
new Map([
["a", 1],
["b", 2],
])
).toEqual(jasmine.mapContaining(new Map([["a", 1]])));
jasmine.objectContaining
jasmine.objectContaining
用於檢查物件是否包含在 Object
裡的 Key
& value
:
expect({ a: 1, b: 2 }).toEqual(jasmine.objectContaining({ a: 1 }));
jasmine.setContaining
jasmine.setContaining
用於檢查 Set 是否包含某元素:
expect(new Set([1, 2, 3])).toEqual(jasmine.setContaining(1));
jasmine.stringMatching
jasmine.stringMatching
用於檢查變數是否符合某正規表達式:
expect("abc").toEqual(jasmine.stringMatching(/abc/));
Spies
spy
是一個函式, 可以監聽其他函式的呼叫情況.
spyOn
spyOn
用在原本就 有物件 並且該物件 也有方法, 這樣可以直接 spy 該物件的方法:
const car = {
run:() => { do something ... };
};
spyOn( car , 'car');
jasmine.createSpy
jasmine.createSpy
用在原本就 有物件, 但 不管有無方法, 都可以幫忙建立一個 spy 的方法:
const car = { // 有方法
run:() => { do something ... };
};
car.run = jasmine.createSpy();
const car = {}; // 或者沒有方法也可以使用
car.run = jasmine.createSpy();
jasmine.createSpyObj
jasmine.createSpyObj
用在原本就 沒有物件, 幫建立一個物件和多個 spy 的方法:
const car = jasmine.createSpyObj("car", ["run", "fly"]);
car.run.and.callFake(() => "Here we go!!!");
car.fly.and.callFake(() => "Boom!!!");
spyOnProperty
spyOnProperty
spy 物件的 getter
或 setter
方法:
const car = {
_speed: 0,
get speed() {
return this._speed;
},
set speed(value) {
this._speed = value;
},
};
spyOnProperty(car, "speed", "get").and.callFake(() => 100);
spyOnProperty(car, "speed", "set").and.cllFake((200) => console.log('do something ...'));
spyOnAllFunctions
spyOnAllFunctions
spy 物件的所有方法:
const car = {
run:() => { do something ... };
fly:() => { do something ... };
};
spyOnAllFunctions(car);
expect(car.run).toHaveBeenCalled();
expect(car.fly).not.toHaveBeenCalled();
Spy.withArgs
withArgs
用於設置 spy 方法的參數
withArgs
withArgs
用在 spy 的方法有多個參數時, 可以指定要 spy 的參數做不同的事情:
const mockData = "bbbb";
const mockData2 = "cccc";
const obj = {
getData: (key) => "aaaa",
};
spyOn(obj, "getData")
.withArgs("b")
.and.returnValue(mockData)
.withArgs("c")
.and.returnValue(mockData2);
expect(obj.getData("b")).toBe(mockData);
expect(obj.getData("c")).toBe(mockData2);
Spy.and
and
用在設置 spy 對象的行為
and.callThrough
and.callThrough
會執行原本的方法:
const obj = {
getData: (key) => "aaaa",
};
spyOn(obj, "getData").and.callThrough();
expect(obj.getData("b")).toBe("aaaa");
and.callFake
and.callFake
會執行自定義的方法:
const obj = {
getData: (key) => "aaaa",
};
spyOn(obj, "getData").and.callFake(() => "bbbb");
expect(obj.getData("b")).toBe("bbbb");
and.returnValue
and.returnValue
會回傳自定義的值:
const obj = {
getData: (key) => "aaaa",
};
spyOn(obj, "getData").and.returnValue("bbbb");
expect(obj.getData("b")).toBe("bbbb");
and.returnValues
and.returnValues
會回傳一系列自定義的值:
const obj = {
getData: (key) => "aaaa",
};
spyOn(obj, "getData").and.returnValues("bbbb", "cccc"); // 按照提供的順序逐個返回
expect(obj.getData("b")).toBe("bbbb");
expect(obj.getData("c")).toBe("cccc");
and.stub
and.stub
用於將 Spy 對象配置為使用原始的樣子, 而不是模擬或是替代行為,
當調用 and.stub
, 會清除任何先前的 Spy 設定並恢復對原始對象或方法的調用:
const obj = {
getData: (key) => "aaaa",
};
spyOn(obj, "getData").and.returnValue("bbbb");
expect(obj.getData("b")).toBe("bbbb");
spyOn(obj, "getData").and.stub(); // 恢復成原本的方法
expect(obj.getData("b")).toBe("aaaa");
and.throwError
and.throwError
會拋出錯誤:
const obj = {
getData: (key) => "aaaa",
};
spyOn(obj, "getData").and.throwError("error");
expect(() => obj.getData("b")).toThrowError("error");
Spy.calls
calls
用於檢查 spy 方法的調用情況
calls.any
calls.any
spy 方法是否被調用過, 會回傳 true
或 false
:
const obj = {
getData: (key) => "aaaa",
};
spyOn(obj, "getData");
expect(obj.getData.calls.any()).toBe(false);
obj.getData("b");
expect(obj.getData.calls.any()).toBe(true);
calls.all
calls.all
回傳全部 spy 方法被調用時的紀錄:
const obj = {
getData: (key) => "aaaa",
};
spyOn(obj, "getData");
obj.getData("b");
obj.getData("c");
expect(obj.getData.calls.all()).toEqual([
{ object: obj, args: ["b"], returnValue: "aaaa" },
{ object: obj, args: ["c"], returnValue: "aaaa" },
]);
calls.allArgs
calls.allArgs
回傳 spy 方法被調用時的所有參數:
const obj = {
getData: (key) => "aaaa",
};
spyOn(obj, "getData");
obj.getData("b");
obj.getData("c");
expect(obj.getData.calls.allArgs()).toEqual([["b"], ["c"]]);
calls.argsFor
calls.argsFor
回傳 spy 方法第幾次被調用時的參數:
const obj = {
getData: (key) => "aaaa",
};
spyOn(obj, "getData");
obj.getData("b");
expect(obj.getData.calls.argsFor(0)).toEqual(["b"]);
calls.count
calls.count
回傳 spy 方法被調用的次數:
const obj = {
getData: (key) => "aaaa",
};
spyOn(obj, "getData");
expect(obj.getData.calls.count()).toBe(0);
obj.getData("b");
expect(obj.getData.calls.count()).toBe(1);
calls.first
calls.first
回傳 spy 方法第一次被調用時的紀錄:
const obj = {
getData: (key) => "aaaa",
};
spyOn(obj, "getData");
obj.getData("b");
obj.getData("c");
expect(obj.getData.calls.first()).toEqual({
object: obj,
args: ["b"],
returnValue: "aaaa",
});
calls.mostRecent
calls.mostRecent
回傳 spy 方法最後一次被調用時的紀錄:
const obj = {
getData: (key) => "aaaa",
};
spyOn(obj, "getData");
obj.getData("b");
obj.getData("c");
expect(obj.getData.calls.mostRecent()).toEqual({
object: obj,
args: ["c"],
returnValue: "aaaa",
});
calls.reset
calls.reset
用於重置 spy 方法的調用情況:
const obj = {
getData: (key) => "aaaa",
};
spyOn(obj, "getData");
obj.getData("b");
obj.getData("c");
expect(obj.getData.calls.count()).toBe(2);
obj.getData.calls.reset();
expect(obj.getData.calls.count()).toBe(0);
Clock
jasmine.clock
用於模擬時間
install
jasmine.clock().install
安裝一個 clock:
beforeEach(() => {
jasmine.clock().install();
});
uninstall
jasmine.clock().uninstall
解除一個 clock:
afterEach(() => {
jasmine.clock().uninstall();
});
tick
jasmine.clock().tick
快轉一段時間:
beforeEach(() => {
jasmine.clock().tick(50);
});
mockDate
jasmine.clock().mockDate
mock 現在時間為某某時間:
beforeEach(() => {
jasmine.clock().mockDate(new Date(2013, 9, 23));
});
Done
done
用於異步測試
done
done
用於異步測試, 等異步有反應後, 通知執行驗證:
it("示範測試異步程式碼", (done) => {
let a = 0;
setTimeout(() => {
a = 100;
expect(a).toBe(100);
done(); // 等待callback後,通知驗證 (非快轉,真的等3秒)
}, 3000);
expect(a).toBe(0);
});
使用 done()
是會等 callback
時間的,
所以要注意每個 spec
預設等待驗證時間為 5s,
超過的話 spec
還是報錯,
因此若要調整 spec
的驗證時間,
則可以使用 jasmine.DEFAULT_TIMEOUT_INTERVAL
去修改 spec
等待驗證的時間