前言
也有一種狀況是:依賴的服務元件資料的取得,是透過呼叫 API 等待伺服器吐資料的非同步行為,那麼這又該如何進行測試呢?
帶有非同步服務的元件
改寫上一個範例,當點選 welcomeComponent 元件內的登入按鈕時,會以 .subscribe() 的形式觸發 user 服務元件的 getData() 方法取得資料,最後顯示出歡迎提示,並且停用登入按鈕。
welcome.component 的 Template
1  | <h3 class="welcome" *ngIf="data.isLoggedIn"><i>{{data.user}},{{data.message}}!</i></h3>  | 
welcome.component 的 class
1  | import { Component, OnInit } from '@angular/core';  | 
user.service
1  | import { Injectable } from '@angular/core';  | 
如何測試帶有非同步服務的元件
這個範例測試的重點是:
- 元件上的登入按鈕的 click 觸發事件是否有效
- 意思是當透過點擊事件觸發元件內的 
login()時,方法真的有被呼叫 
 - 意思是當透過點擊事件觸發元件內的 
 - 元件的渲染是不是正常的
- 意思是獲得資料後元件有正確的顯示
 
 
測試環境建置
於是我們可以像先前一樣,使用 jasmine.createSpyObj() 產生一個假的 getData() 方法,並且預先建立好一組假的資料 - data 。
welcome.component.spec
1  | import { async, ComponentFixture, TestBed } from '@angular/core/testing';  | 
撰寫測試
前置作業準備完畢,開始撰寫測試吧。
第一個測試
- 元件上的登入按鈕的 click 觸發事件是否有效
- 意思是當透過點擊事件觸發元件內的 
login()時,方法真的有被呼叫 
 - 意思是當透過點擊事件觸發元件內的 
 
1  | it('觸發 Click 事件後,是否有正常呼叫 getData() ', () => {  | 
第二行的意思是,當假的 getData 方法被呼叫時必須回傳一個觀察者物件 (Observable) 。
因為第三行觸發元件內的 login 方法時
- user 服務元件內的 
getData()被觸發了,並且使用.subscribe()方法訂閱 - 而我們在測試時使用的 
getData()是假造的替身,所以必須回傳一個觀察者物件才可以使用.subscribe(),否則會出錯導致測試失敗 

第二個測試
- 測試元件的渲染是正常的
- 意思是獲得資料後元件有正確的顯示
- 像是當 
isLoggedIn為true時,登入按鈕為disabled 
 - 像是當 
 
 - 意思是獲得資料後元件有正確的顯示
 
1  | it('未登入時元件的渲染', () => {  | 
而這部分測試的關鍵在於「順序」,像是:
component.data = data;當我們把假資料重新賦值給元件內的data後- 必須呼叫 
fixture.detectChanges();重新進行資料與元件間的繫結- 如此才可以使用 
fixture.nativeElement.querySelector()找到指定目標 
 - 如此才可以使用 
 
- 必須呼叫 
 
fakeAsync() 進行非同步測試
在這個範例內,我並沒有實際的呼叫 API ,而是在 user 服務元件內透過 setTimeout() 模擬呼叫 API 時等待伺服器的時間。
而如果也想在測試過程中模擬這一段情境的話,可以使用 fakeAsync() 。
1  | it('非同步測試渲染情形', fakeAsync(() => {  | 
這個測試使用了 setTimeout() 方法,令 data 物件內的屬性三秒後變更,並且重新賦值給元件內的 data 屬性,最後重新繫結。
而這個測試另一個關鍵是 tick() , tick() 函式接受一個毫秒值作為參數(如果沒有提供則預設為 0)。
該參數表示虛擬時鐘要前進多少,也就是說:
setTimeout()如果時間設置 3000 ,那麼tick()也要設置 3000
這樣才能正確取得資料。


至此完成了對元件的測試。