avatar

目錄
Angular Testing(Part 3)- Service Test

Hi,大家好,前兩篇文章簡單介紹了 Angular CLI 一手裝好裝滿的單元測試環境,分別淺淺的講了 Jasmine 及 Karma,如何啟動測試,Angular CLI 大概的單元測試配置檔,以及 TestBed.configureTestingModule() 定義了類似 @NgModule 應用的來打造測試環境,今天要針對 service 的測試進行練習

Service 服務

服務是定義了特殊用途的 class,從伺服器獲取資料、驗證使用者輸入或直接往控制檯中寫日誌等工作會委託給各種服務

服務就是一個 class,只是在 Angular 之中此 class 帶有 @Injectable 這個 decorator,利用 Angular 獨有的模組化方式來使用這個服務,透過依賴注入來幫你更容易地將應用邏輯分解為服務

ng generate service

就來建立一個 service 吧!直接在終端機輸入 ng generate service Value 即可

就會得到這兩個 .ts 檔

service 產生後的初始狀態就是這樣,帶有 @Injectable 的 class,就可以直接對 class 填進所需要的 property 或 method 來進行設計,測試檔的部分寫好了 TestBed 設定完並 inject 進 ValueService 的實體,能直接進行撰寫測試項目

對同步執行的 pure function 回傳值進行測試

在 value.service.ts 撰寫一個 method

typescript
1
2
3
public getValue(): string {
return 'real value';
}

在 value.service.spec.ts 撰寫相應的測試

typescript
1
2
3
it('getValue should return real value', () => {
expect(service.getValue()).toBe('real value');
});

執行測試

對非同步執行的 Observable 回傳值進行測試

在 value.service.ts 撰寫一個假裝 1 秒後回傳值的 Observable

typescript
1
2
3
4
5
6
7
8
public getObservableValue(): Observable<string> {
return new Observable((subsriber: Subscriber<string>): void => {
setTimeout((): void => {
subsriber.next('observable value');
subsriber.complete();
}, 1000);
});
}

在 value.service.spec.ts 撰寫相應的測試

因為是非同步回傳的行為,必須要在測試的函式中使用 done: DoneFn 這個參數進行 callback 呼叫來告知測試要結束的時機

測試 Observable 就直接訂閱 subscribe 下去,在 done、error 或 complete 的函式中進行測試

typescript
1
2
3
4
5
6
7
8
it('getObservableValue should return value from observable', (done: DoneFn) => {
service.getObservableValue().subscribe(
(value) => {
expect(value).toBe('observable value');
done();
},
);
});

執行測試

對非同步執行的 Promise 回傳值進行測試

在 value.service.ts 撰寫一個假裝 1 秒後回傳值的 Promise

typescript
1
2
3
4
5
6
7
public getPromiseValue(): Promise<string> {
return new Promise((resolve): void => {
setTimeout((): void => {
resolve('promise value');
}, 1000);
});
}

在 value.service.spec.ts 撰寫相應的測試

跟剛才的 Observable 一樣有非同步回傳的行為,在測試函式中使用 done: DoneFn 來告知測試要結束的時機

typescript
1
2
3
4
5
6
7
it('getPromiseValue should return value from a promise', (done: DoneFn) => {
service.getPromiseValue()
.then((value) => {
expect(value).toBe('promise value');
done();
});
});

執行測試

非同步行為如果測試沒有執行到 done()

typescript
1
2
3
4
5
6
7
it('getPromiseValue should return value from a promise', (done: DoneFn) => {
service.getPromiseValue()
.then((value) => {
expect(value).toBe('promise value');
// done();
});
});

執行的測試會跑了 5 秒後告訴你測試 timeout 並失敗了

所以非同步行為的測試撰寫要特別注意,偏偏網頁前端通常都有大量的非同步行為,要撰寫針對非同步行為來寫測試的這種情形一定會常常遇到

非同步行為的測試有一個 !?

這裡把測試項目中的 done() 給拿掉,並且連測試函式的參數 done: DoneFn 都不放

typescript
1
2
3
4
5
6
it('getPromiseValue should return value from a promise', () => {
service.getPromiseValue()
.then((value) => {
expect(value).toBe('promise value');
});
});

結果…

測試居然綠油油的通過了!?

等等等,仔細看一下瀏覽器上那一列顯示的文字

有一條測試項目標明 SPEC HAS NO EXPECTATIONS,代表這個測試項目完全沒執行到任何 expect 的函式,等於是一段無效的測試,這應該會是未來需要特別留意的,不確定為何沒有設計成跳 failure 並顯示紅色來讓它更容易察覺,也可能有其他地方可以更改設置,留待未來覺得這種顯示雷到受不了時就會解決它

總結

今天只簡單的對最基本會使用的 method 來撰寫測試,回顧一下今天這篇文的一些重點

  • 測試 service 要先用 TestBed 來 inject 取得 service 測試用的服務實體
  • 對同步行為的 function 撰寫測試時就很一般逐行寫上測試需求,最後斷言,但對到有非同步行為的 function 時要記得在非同步回傳值後告知測試執行 done() 來結束測試,否則會 timeout 測試失敗
  • 要注意 SPEC HAS NO EXPECTATIONS 這個關鍵字,雖然是測試項目列顯示是綠色的,但這可是無效的測試無誤

下一篇還是寫對 service 進行測試,是關於有依賴的 service,有時會需要將依賴給隔離而用到 spy,為了讓單元測試能夠獨立執行而不會被其他依賴干擾所使用的技巧

文章作者: 小馬彬
文章鏈接: https://littlehorseboy.github.io/2020/04/26/2020-angular-testing-part-3/
版權聲明: 本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 4.0 許可協議。轉載請註明來自 小馬彬的部落格

評論