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