iT邦幫忙

2022 iThome 鐵人賽

DAY 13
2
Modern Web

今天我想來在 Angular 應用程式上加上測試保護系列 第 13

Day 13 - 單元測試 - 測試 Angular 元件 - 非同步驗證測試

  • 分享至 

  • xImage
  •  

前言

上一篇撰寫了表單元件的測試程式,今天進一步來針對表單內有非同步驗證時,要如何撰寫單元測試程式。

範例程式

今天的範例程式會使用 LoginPageCompnent 頁面元件,此頁面元件提供使用者進行登入作業。與先前一樣,在測試模組上加入 Angular Material 模組;並建立一 AuthenticationService 的 Spy 服務物件,來替代原來的登入驗證服務。

let authService: jasmine.SpyObj<AuthenticationService>;

beforeEach(async () => {
  authService = jasmine.createSpyObj('AuthenticationService', ['isExists']);
  authService.isExists.and.returnValue(of(false).pipe(delay(1000)));

  await TestBed.configureTestingModule({
    imports: [
      NoopAnimationsModule,
      ReactiveFormsModule,
      MatCardModule,
      MatFormFieldModule,
      MatInputModule,
      MatIconModule,
    ],
    declarations: [LoginPageComponent],
    providers: [{ provide: AuthenticationService, useValue: authService }],
  }).compileComponents();

  fixture = TestBed.createComponent(LoginPageComponent);
  component = fixture.componentInstance;
  fixture.detectChanges();
});

如上面程式,為了模擬非同步作業,讓 AuthenticationServiceisExists 方法延遲了 1 秒才回傳結果。

利用 fakeAsync 檢查使用者不存在錯誤訊息

針對這種包含著非同步作業的元件,Angular 提供了 fakeAsync 方法,搭配著 tick() 函式的使用,讓我們可以在單元測試程式以線性的方式撰寫,而非使用 Promise.then() 的巢狀語法來增加控制流的複雜度。

import { fakeAsync, tick } from '@angular/core/testing';

it('利用 fakeAsync 方法測試非同步作業', fakeAsync(() => {
  const startTime = Date.now();
  tick(500);
  const endTime = Date.now();
  expect(endTime - startTime).toBe(500);
}));

透過 tick() 方法可以讓我們推進測試時的模擬時間,此方法第一個參數用來指定要推進的毫秒數,預設為 0。

it('當帳號不存在時, 應顯示錯誤訊息為 "此帳號不存在"', fakeAsync(() => {
  // Arrange
  const formFieldElement = fixture.debugElement.query(
    By.directive(MatFormField)
  );
  const inputElement: HTMLInputElement = formFieldElement.query(
    By.css('input')
  ).nativeElement;

  // Act
  inputElement.value = 'oliver';
  inputElement.dispatchEvent(new Event('input'));
  inputElement.dispatchEvent(new Event('blur'));
  tick(1000);
  fixture.detectChanges();

  // Assert
  const errorElement = formFieldElement.query(By.directive(MatError));
  expect(errorElement.nativeElement.textContent.trim()).toBe('此帳號不存在');
});

因此,我們可以如上面程式,在 it 的測試主體中使用了 fakeAsync(() ⇒ {}) ,來測試使用者帳號是否存在這種非同步的需求。如一開始所說,此非同步的驗證會延遲了 1 秒才回傳檢果;所以在測試主體內使用了 tick(1000) ,讓測試內的模擬時間推進 1 秒,再去觸發 Angular 的檢測變更以更新頁面。

利用 whenStable 檢查使用者不存在錯誤訊息

除了利用 tick() 方法來模組時間的推進,也可以利用 ComponentFixture 內的 whenState 方法來實際等待非同步作業完成。因此,我們可以搭配著 async 方法,透過 whenStable() 方法來在非同步作業完成後,才去觸發 Angular 變更檢測,以及後續測試情境的驗證。

it('當帳號不存在時, 應顯示錯誤訊息為 "此帳號不存在"', async () => {
  // Arrange
  const formFieldElement = fixture.debugElement.query(
    By.directive(MatFormField)
  );
  const inputElement: HTMLInputElement = formFieldElement.query(
    By.css('input')
  ).nativeElement;

  // Act
  inputElement.value = 'oliver';
  inputElement.dispatchEvent(new Event('input'));
  inputElement.dispatchEvent(new Event('blur'));
  await fixture.whenStable();
  fixture.detectChanges();

  // Assert
  const errorElement = formFieldElement.query(By.directive(MatError));
  expect(errorElement.nativeElement.textContent.trim()).toBe('此帳號不存在');
});

執行測試程式

最後就執行 ng test 來確認測試執行的結果。

https://ithelp.ithome.com.tw/upload/images/20220928/20109645s7FWP9BfUy.png

接下來

這一篇利用 fakeAsynctick 來測試有非同步作業的元件,完整的測試程式可以參考 GitHub。接下來,來說明當元件包含了路由的切換,要如何撰寫單元測試。


上一篇
Day 12 - 單元測試 - 測試 Angular 元件 - 測試表單元件
下一篇
Day 14 - 單元測試 - 測試 Angular 元件 - 路由測試
系列文
今天我想來在 Angular 應用程式上加上測試保護30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言