[UT] Angular 召喚 Karma + Jasmine 來做個單元測試吧 II

Huge Gun
10 min readApr 21, 2020

--

這篇是 Unit test 系列的第三篇,主角依舊是三大框架之一的 Angular,這篇會講進階的,更貼近實際開發時的寫法,若你還沒看過前兩篇基礎的部分,請看這[第一篇] [第二篇],接下來我會區分成以下四個重點來說:

  1. Config 更正式的測試環境
  2. Component
  3. Service
  4. Pipe

上述 2~4 個大類中有幾個不同的案例,開始前你可以先 clone code [Github],先說一下整包的架構,主要看 app / case 兩個 folder,app 就如我們熟知的 angular root module 一樣沒變,case 則是對應不同的 test case ,有常見的 component 、service、pipe,每個之下都是獨立元件,且都有 UT file,那我們就開始吧。

Config 更正式的測試環境

第二篇結尾的 Counter 範例,它只是個很單純的 class component 可以直接 new 出來,但實際開發中的元件可能會有 module,而 module中又掛入 provider service、http service 之類的,或是會有跟 DOM 互動的行為啦,父子層的連動啦 等等…要模擬跟測試這樣複雜的 component,通常會用官方給的一些工具

TestBed = Testing environment tool:

它是 Angular 官方提供用來設定測試環境的方法,TestBed 讓我們用最簡單的方式配置一個測試用的 NgModule,包括 declarations、providers、imports 都可模擬,配置如下:

TestBed.configureTestingModule({    
imports: [ Dependency ModuleName, ],
providers: [ Dependency ServicesName ],
declarations: [ Current ComponentName, Dependency ComponentName ]
});

我們用 TestBed 修改 Counter 例子來看看

上面的 code 也可以在 cases/component/counter-testbed 路徑找到對應的 file,補充說明一下 angular/core/testing 中取用的另外兩個工具

  1. async: 它接受一個 function 通常是用來 config test module,完成後會 return 配置好的 module 給 TestBed。
  2. ComponentFixture: 它是一個 class,搭配 TestBed.createComponent() 使用,會建立一個受測 Component 的元件,把相應元素新增到 test runner 的 dom中,再 return 一個 ComponentFixture object,這個 object 就是可以拿來做各種 test 的受測體了。

使用 ComponentFixture 該注意的點: 當 call 了 createComponent 後就不能再重新配置 TestBed 了,等於受測體已經建立,想重新配置只能先 call TestBed.compileComponents 關閉當前的 TestBed 受測體實例才行,至於其他建立後的特殊限制請參考[官方文件]

說完了實際開發時 UT 的配置法 & 工具了,後續案例就不再說配置這塊了,接下來就直接進入 test case 吧!!

Component

這個大類會有幾個不同的 test case,每隻 file 都只會有些許 test 範例,讓 demo 易讀一點,實戰中可以比照各個 test case 去組合再自己檔案上,我們從個求梯形面積的小 case 慢慢到進階來看。

  1. Case: parameter (有參數); 路徑: cases/component/parameter

File name: parameter.component.ts

File name: parameter.component.spec.ts

有參數的測試很簡單,從 TestBed 拿到 component 實例之後就可測試,沒什麼特別的。

2. Case: From (表單); 路徑: cases/component/form

File name: from.component.ts

我們模擬一個 login from 表單,它有三個欄位 name、password、email,再一開始就會被創建,分別具有必填、最少8個字元長度、mail 格式的 input 規則限制,接著看 UT 的測試步驟

File name: from.component.spec.ts

Step-1. 在 beforeEach 每次跑測試之前,執行一次初始化 component ,以保持每段測試都是最開始、最乾淨的狀態。

Step-2. 一如既往的先測 component 能否被初始化。

Step-3. 上述的三個欄位,是否有被正確 render,所以使用了 .contains 取得 dom 來測。

Step-4. 對 name 欄位的必填設定做測試,當 setValue 給空值後,確認是否 toBeFalsy,pass 的話就表示為不合法,有成功阻擋空值。

Step-5. 對 password 欄位的字元長度做測試,當 setValue '12345678' 後,確認是否 toBeTruthy,pass 的話就表示為符合。

Step-6. 對 email 欄位的格式做測試,當 setValue 'hello@gmail.com'後,確認是否 toBeTruthy,pass 的話就表示為合法。

from 例子都採取單向的測試,就是正向反向只取一種來測即可,例如 name 欄位必填測試,已經測過空值,就無需再寫一個 it 去測有值的情境了。

3. Case: Event Emitter; 路徑: cases/component/event-emitter

File name: event-emitter.component.spec.ts

這個是常用將子元件事件送出給父元件的裝飾器,它是個可觀察的 object,只要是用可觀察性質的測試,都可以借鑒以下的寫法

File name: event-emitter.component.ts

Step-1. 跟前述都一樣,在 beforeEach 每次跑測試之前,執行一次初始化 component 。

Step-2. 一如既往的先測 component 能否被初始化。

Step-3. 從被實例化的 component 中取出 counterChanged 這個裝飾器,並且訂閱它之後,呼叫它有的 incrementCounter 確認值是否正確,非常的簡單。

Service

service 這大類會有幾個不同的 test case,我們先從最簡單的開始,如下:

  1. Case: Simple Service; 路徑: cases/service/simple

File name: simple.service.ts

它的配置基本上跟 component 一樣,只是在 providers 的地方換成配置 service,其餘都一樣

File name: simple.service.spec.ts

Step-1. 跟前述都一樣,在 beforeEach 每次跑測試之前,執行一次初始化 service。

Step-2. 一如既往的先測 service 能否被初始化。

Step-3. 從被實例化的 service,呼叫它有的 getServiceValue 確認值是否正確,非常的簡單。

2. Case: Http Service; 路徑: cases/service/http

service 經常拿來做跟後端互動等等…不屬於 component 負責的邏輯處理,今天我們用 call api 當作例子,來看看怎麼寫 UT

File name: http.service.ts

這邊我有起一個 json-server 來模擬,如果不知道它是啥的小夥伴可以看[這邊],其餘的就跟使用 HttpClient 那樣任何沒有區別

File name: http.service.spec.ts

測試檔這邊重複出現 or 比對(例如: toBe) 這類的使用,後續就不再多說,前述都已經講過了,至於比較不同的是處理 http UT 時,正向反向都會測,因為 user 再操作時,page 會需要 render 成功 or 失敗的相應畫面,所以等於就是兩個不同的情境了。

Step-1. 跟前述都一樣,在 beforeEach 每次跑測試之前,執行一次初始化 service。

Step-2. 一如既往的先測 service 能否被初始化。

Step-3. 測試成功情境,從被實例化的 service,呼叫它有的 simulationPostCall,塞入 request 並檢查返回的 data 種種驗證。

Step-4. 使用 expectOne 取出 request (包含 http header 等等…)做驗證。

Step-5. 使用 flush 對 httpTestingController 模擬出來的 apiRequest 清空,以確保下個測試的 http 是乾淨的。

Step-6. 測試失敗情境,和成功情境的操作幾乎一樣。

Step-7. 一樣使用 flush 對 httpTestingController 模擬的 apiRequest 清空。

3. Case: Injector Service; 路徑: cases/service/injector

開發中有時會遇到自己的 service 注入其他的 service,這邊會演示怎麼測試這種 injector 的狀況,我們會有2個 service,如下:

File name: common.service.ts (配角;會被注入到主角中)

配角有個對外的 getCommonServiceValue function,接著主角會需要呼叫到,接著我們把它注入到主角中

File name: injector.service.ts (主角)

File name: injector.service.spec.ts (主角的測試)

上面的例子我們用 Jasmine 提供的 SpyOn 來模擬物件,它可以攔截原本的方法呼叫,改呼叫 SpyOn 產生出來的假物件及方法,如此可以斷開物件相依,方便我們進行 UT,也就以利測這種有注入的 service

Step-1. 跟前述都一樣,在 beforeEach 每次跑測試之前,執行一次初始化 service。

Step-2. 一如既往的先測 service 能否被初始化。

Step-3. 測試本身自己的 service 的 getInjectorServiceValue 值是否正確。

Step-4. 測試注入的common service 它的 callCommonServiceGetValue 值是否正確。

Pipe

官方內建的 pipe 一般都有測過,無須在測一次,這邊會演示自定義的 pipe 如何寫 UT,我們建一個轉換手機號碼格式的例子,這個 pipe 會將號碼,例如: 0911222333 轉成有 - 的 0911–222–333

  1. Case: Custom Pipe; 路徑: cases/pipe/custom

File name: custom.pipe.ts

File name: custom.pipe.spec.ts

Step-1. pipe 通常都是純 class,直接用 new 初始化即可。

Step-2. 一如既往的先測 pipe 能否被初始化。

Step-3. 測試手機轉換的格式值是否正確。

以上就是 Angular 做 UT 時,比較常遇到的情境啦,這些範例 code 互相組合大概可以涵蓋8成左右的測試了,下一篇再來補充幾個較少用 case 的測試啦!! 感謝。

UT 系列:

[UT] What’s unit test ? 在前端要測什麼 ?!

[UT] Angular 召喚 Karma + Jasmine 來做個單元測試吧 I

[UT] Angular 召喚 Karma + Jasmine 來做個單元測試吧 II

[UT] Angular 召喚 Karma + Jasmine 來做個單元測試吧 III

--

--

Huge Gun

槍再大把,沒子彈是不行的;通過學習,鍛造自己的子彈!https://github.com/HsienW