最近很多人在瘋 Angular 測試,那官網程式學習筆記006 就來看官方是怎麼寫測試,這次只會先看 Angular Team 是怎麼測試service 類的程式碼
研究對象
document.service.spec.ts
是這次研讀的對象,這是一個單純的 service,他的功能很簡單 (程式行數 100 行內),就是將文件顯示到畫面上。
document.service.ts
的程式碼
1 | import { Injectable } from '@angular/core'; |
- 這一個 service 有注入 3 個東西,
Logger
、Http
、LocationService
,這些在測試程式碼內也是需要被處理的
spec
初始化
Angular Team 為了這個 document.service
另外寫了兩個 function 來建立要測試的實體
1 | function createInjector(initialUrl: string) { |
-
利用
ReflectiveInjector
建立Injector
,當建立Injector
時,也同時會處理 DI 的部分 -
透過
Injector.get
的方式取得provider
的實體 -
const { docService } = getServices()
是 Object 解構子的寫法 -
MockLogger
是共用的測試 Mock 模型1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import { Injectable } from '@angular/core';
()
export class MockLogger {
output = {
log: [],
error: [],
warn: []
};
log(value: any, ...rest) {
this.output.log.push([value, ...rest]);
}
error(value: any, ...rest) {
this.output.error.push([value, ...rest]);
}
warn(value: any, ...rest) {
this.output.warn.push([value, ...rest]);
}
} -
利用巢狀
describe
的方式將要測試的項目分組,提供更好的閱讀體驗
測試 Http
1 | docService.currentDocument.subscribe(); |
-
這動作會觸發
fetchDocument
的私有方法,但這裡並不直接測試fetchDocument
,而是間接測試中間過程可能引發的變化 -
fetchDocument
會做http.get
的行為,所以利用MockBackend
可以取得呼叫Http
時的相關資訊1
2
3
4
5
6
7
8
9it('should fetch a document for the initial location', () => {
const { docService, backend } = getServices('initial/doc');
const connections = backend.connectionsArray;
docService.currentDocument.subscribe();
expect(connections.length).toEqual(1);
expect(connections[0].request.url).toEqual(CONTENT_URL_PREFIX + 'initial/doc.json');
expect(backend.connectionsArray[0].request.url).toEqual(CONTENT_URL_PREFIX + 'initial/doc.json');
}); -
docService.currentDocument
裡面有包含一個switchMap
,所以當路徑變化時,也會重新取得文件內容-
測試程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17it('should emit a document each time the location changes', () => {
let latestDocument: DocumentContents;
const doc0 = { contents: 'doc 0', id: 'initial/doc' };
const doc1 = { contents: 'doc 1', id: 'new/doc' };
const { docService, backend, locationService } = getServices('initial/doc');
const connections = backend.connectionsArray;
docService.currentDocument.subscribe(doc => latestDocument = doc);
expect(latestDocument).toBeUndefined();
connections[0].mockRespond(createResponse(doc0));
expect(latestDocument).toEqual(doc0);
locationService.go('new/doc');
connections[1].mockRespond(createResponse(doc1));
expect(latestDocument).toEqual(doc1);
}); -
createResponse
用來建立模擬回傳結果的一個方法1
2
3function createResponse(body: any) {
return new Response(new ResponseOptions({ body: JSON.stringify(body) }));
} -
利用
locationService.go
來做網址的切換
-
-
模擬
Http Fail
的狀況1
connections[0].mockError(new Response(new ResponseOptions({ status: 404, statusText: 'NOT FOUND'})) as any);
mockError
會造成Http
呼叫產生Exception
-
mockBackend.connectionsArray
- 這一個陣列會在執行任何
Http
呼叫後,才會有值 - 使用陣列的原因是,如果一個動作裡面有呼叫多個
Http
時,就可以針對個別的 Connection 給予不同的mockRespond
- 使用這種方式,可以減少
subscribe
的次數,以接近同步的方式寫測試
- 這一個陣列會在執行任何
重點回顧
- 測試
service
不一定需要使用TestBed
的方式來建立serivce
實體,可以透過ReflectiveInjector.resolveAndCreate([])
的方式建立Injector
,進而使用injector.get
的方式取得service
實體 - 如果要測試
HttpClient
,可以透過MockBackend
內建的模組來模擬回傳結果或錯誤結果 - 利用
mockBackend.connectionsArray
的方式取得每次Http
呼叫時所建立的連線,並給予相對應的模擬資料 ,須留意取得的順序