最近很多人在瘋 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呼叫時所建立的連線,並給予相對應的模擬資料 ,須留意取得的順序