深入探討 Jasmine 的 Spy
Jasmine 是個 mock 與 assertion 合一的 testing framework,語法優美,寫起來程式碼的可讀性很高,重點是不複雜,學習曲線平緩,Spy 為 Jasmine 最重要的部分,本文針對最常在 Angular 使用的 Spy 做整理。
Version
Jasmine 2.6.2
Angular 4.3
建立 Spy
spyOn()
spyOn
(object: T, method: keyof T): Spy
對於 class 的 method 建立 Spy。
1 | it(`should use getTitle() method`, () => { |
測試在 HTML getTitle() 是否 binding 正確。
spyOnProperty()
spyOnProperty
(object:T, property: keyof T, accessType: string): Spy
對於 class 的 property 建立 Spy。
1 | it(`should use title in HTML`, () => { |
測試在 HTML title 是否 binding 正確。
jasmine.createSpy()
createSpy(name: string, originalFn?: Function): Spy
對於 function 或 arrow function 建立 Spy。
1 | it(`should call arrow function`, () => { |
測試 function 或 arrow function 是否被呼叫。
若要建立在 class 內的假 method,要用
spyOn();若要建立沒有 class 的假 function 或 arrow function,則要使用jasmine.createSpy()。
Spy#and
returnValue()
returnValue(val: any): Spy
建立假 function 的假回傳值。1
2
3
4
5
6
7it(`should use getTitle()`, () => {
const spy = spyOn(component, 'getTitle').and.returnValue('My Todos');
fixture.detectChanges();
htmlElement = debugElement.query(By.css('h1')).nativeElement;
expect(htmlElement.textContent).toBe('My Todos');
});
使用 spyOn() 搭配 returnValue() 回傳假值,然後測試結果是否為假值。實務上有 2 個場景會使用 returnValue()。
- 單元測試時,想要隔離相依物件,使用
returnValue()建立假回傳值。 - 整合測試時,想要測試關係,不是測試結果,藉由假資料測試 function 是否被呼叫到。
1 | it(`should use getTitle()`, () => { |
若只是測試關係,那有更簡單的寫法,使用 spyOn() 建立 Spy 之後,測試 Spy 的 toHaveBeenCalled() 即可,不需使用 returnValue()。
callFake()
callFake(fn: Function): Spy
建立假 function 的假實作。
1 | it(`should use getTitle() and result is 'my todos'`, () => { |
一般來說,spyOn() 搭配 returnValue() 已經非常夠用,若你想自行建立 Fake 取代原本 function 實作,則可使用 callFake()。
callThrough()
callThrough(): Spy
依然使用原本 function,但會針對此 function 做 Spy 監測。
1 | it(`should use getTitle() and result is 'todos'`, () => { |
一般我們使用了 spyOn() 之後,就會搭配 toHaveBeenCalled() 測試有沒有被呼叫到即可,若你想除了驗證關係外,還順便結果,則可搭配 callThrough(),此時會執行實際的 function,所以可使用 toBe() 測試結果,但仍然保有 Spy 功能,因此也可使用 toHaveBeenCalled() 測試關係。
throwError()
throwError(msg: string): Spy
建立假 function 的假 error。
1 | it(`should throw error on 'getTodos()' method`, () => { |
使用 throwError() 建立假 error。
比較特別的是,要測試 error 時,待測 method 要放在 expect() 的 arrow function 內,最後用 toThrowError() 測試 error 是否被觸發。
Spy#calls
any()
any(): boolean
若 Spy 被呼叫一次以上,則傳回 true,否則傳回 false。
1 | it(`should use getTitle()`, () => { |
測試 spy.calls.any(),若曾被呼叫過為 true,否則為 false。
這種寫法等效於:
1 | it(`should use getTitle()`, () => { |
count()
count(): boolean
傳回 Spy 被呼叫的次數。
1 | it(`should use getTitle() for once`, () => { |
測試 spy.calls.count(),可精確測試 Spy 被呼叫的次數。
這種寫法等效於:
1 | it(`should use getTitle() for once`, () => { |
Matcher
toHaveBeenCalled()
toHaveBeenCalled(): boolean
測試 function 是否被呼叫過。
1 | it(`should use getTitle()`, () => { |
測試 Spy 是否被呼叫過。
toHaveBeenCalledTimes()
toHaveBeenCalledTimes(expected: number): boolean
測試 function 是否被呼叫過 n 次。
1 | it(`should use getTitle() for once`, () => { |
測試 Spy 是否被呼叫過 1 次。
Conclusion
- Jasmine 的 Spy 算是 Mock 與 Spy 的合體,提供單元測試與整合測試夠用的武器,在單元測試可以藉由 Spy 隔離相依物件,在整合測試可以藉由 Spy 驗證物件之間的關係。
Reference
KeenWon, Javascript测试框架Jasmine (五):Spies
Jasmine, Jasmine Introduction