前言
介紹完服務元件的測試後,接著要學習如何測試一個單純的元件。
元件測試
在 Angular 中,普通的元件包含了 Template 與 class ,因此想對元件進行充分的測試,勢必得對這兩個部分都進行測試才行。
這些測試需要在瀏覽器的 DOM 中建立元件的宿主元素(就像 Angular 所做的那樣),然後檢查元件的 class 和 DOM 的互動是否如同 Template 中所描述的那樣。
Angular 的 TestBed 為所有這些型別的測試提供了基礎設施。
但是很多情況下,可以單獨測試元件類本身而不必涉及 DOM ,就已經可以用一種更加簡單、清晰的方式來驗證該元件的大多數行為了。
建立一個最單純的元件
測試之前我們要先準備環境,因此建立一個元件 - lightSwitch
- 當用戶點選按鈕時,它會切換燈的開關狀態 (畫面上的文字狀態)
輸入
ng g c lightSwitch
建立元件。
Template
1 | <button (click)="clicked()">Click me!</button> |
class
1 | import { Component, OnInit } from '@angular/core'; |
app.component Template
1 | <app-light-swich></app-light-swich> |
測試元件的 class
可以像先前測試服務元件般,單獨測試元件中的 class 。
- 這個範例中要測試 clicked() 方法能否正確切換燈的開關狀態
- getMessage() 有無回傳合適的訊息
而這個元件的 class 並沒有依賴任何的服務元件,是非常單純的。
這種情況下可以直接 new 出物件實體,進行 isOn
屬性的狀態測試。
light-swich.component.spec
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; |
測試元件的 DOM
完整的元件不只有 class ,元件還要和 DOM 以及其它元件進行互動。
只涉及 class 的測試可以得知元件 class 的行為是否正常,但不能得知元件是否能正常渲染出來、響應使用者的輸入和查詢或與它的父元件和子元件相整合。
要進行完整的測試,我們不得不建立那些與元件相關的 DOM 元素了,必須檢查 DOM 來確認元件的狀態能在恰當的時機正常顯示出來,並且必須透過螢幕來模擬使用者的互動,以判斷這些互動是否如我們預期。
而這部分就需要用到 TestBed 了。
當我們建立元件時, Angular CLI 會幫我們寫好預設的測試
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; |
而有些時候並不需要這些 Angular CLI 幫我們寫好的 Code 。
Angular CLI 是預設我們可能會用到這些東西,而目前我們可以再精簡一些。
1 | describe('LightSwichComponent (minimal)', () => { |
當元件逐漸演變成更加實質性的東西時,才會用到那些 Angular CLI 幫我們預設產生的測試。
在這個例子中,傳給 TestBed.configureTestingModule 的元資料物件中只宣告了 LightSwichComponent - 也就是待測試的元件。
不用宣告或匯入任何其它的東西,預設的測試模組中已經預先配置好了,
比如來自 @angular/platform-browser 的 BrowserModule。
createComponent()
在配置好 TestBed.configureTestingModule() 之後,可以呼叫它的 createComponent() 方法。
TestBed.createComponent() 會建立一個 LightSwichComponent 的元件,把相應的元素新增到 test-runner 的 DOM 中,然後返回一個 ComponentFixture 物件。
特別要注意的是,在呼叫了 createComponent 之後就不能再重新配置 TestBed 了。 createComponent 方法凍結了當前的 TestBed 定義,關閉它才能再進行後續配置。
也就是說不能:
- 呼叫任何 TestBed 的後續配置方法
- 不能調 configureTestingModule()
- 不能調 get()
- 能呼叫任何 override … 方法
如果試圖這麼做,TestBed 就會丟擲錯誤。
ComponentFixture
ComponentFixture 是用來與所建立的元件及其 DOM 元素進行互動。
從剛才的範例程式碼來看,我們可以透過 fixture
來訪問該元件的 instance ,並用 Jasmine 的 expect 語句來確保其存在。
- .toBeDefined()
- 使用 .toBeDefined() 檢查一個變數不是 undefined 。
beforeEach()
隨著元件的成長,可能會有很多組測試。
這時除了一直複製之外,還有個比較好的做法:
- 把重複會用到的程式碼搬到 beforeEach() 內。
因此可以重新調整剛剛那段程式碼:
1 | describe('LightSwichComponent (minimal)', () => { |
nativeElement
可以使用 nativeElement 中獲取元件內的元素,像是:
1 | describe('LightSwichComponent (minimal)', () => { |
console.log(el);
印出來的內容是:
於是可以藉由 .getElementsByTagName() 找到 button 標籤,最後再判斷長度,即可測試這個元件內有沒有按鈕了。
DebugElement
而除了使用 nativeElement 取得元件內的元素之外,也可以透過 DebugElement 取得元件內的元素。
至於為什麼要使用 DebugElement 呢?
以下是官方文件給出的解釋:
nativeElement 的屬性取決於執行環境。 你可以在沒有 DOM,或者其 DOM 模擬器無法支援全部 HTMLElement API 的平臺上執行這些測試。Angular 依賴於 DebugElement 這個抽象層,就可以安全的橫跨其支援的所有平臺。 Angular 不再建立 HTML 元素樹,而是建立 DebugElement 樹,其中包裹著相應執行平臺上的原生元素。 nativeElement 屬性會解開 DebugElement,並返回平臺相關的元素物件。
所以上面那個使用 nativeElement 的測試可以改寫成:
1 | describe('LightSwichComponent (minimal)', () => { |
改寫後的結果會與改寫前完全一樣。
小結
實務上的元件可能不會像範例上一樣這麼單純,可能會依賴某個服務元件之類的…情況,因為再寫下去可能篇幅會過長,所以決定先在這邊設個中斷點。